diff options
636 files changed, 17631 insertions, 10988 deletions
diff --git a/android-sdk-flags/OWNERS b/android-sdk-flags/OWNERS new file mode 100644 index 000000000000..01f45dd01e36 --- /dev/null +++ b/android-sdk-flags/OWNERS @@ -0,0 +1 @@ +include /SDK_OWNERS diff --git a/android-sdk-flags/flags.aconfig b/android-sdk-flags/flags.aconfig index cfe298e187d1..19c7bf674832 100644 --- a/android-sdk-flags/flags.aconfig +++ b/android-sdk-flags/flags.aconfig @@ -6,6 +6,7 @@ flag { namespace: "android_sdk" description: "Use the new SDK major.minor versioning scheme (e.g. Android 40.1) which replaces the old single-integer scheme (e.g. Android 15)." bug: "350458259" + is_exported: true # Use is_fixed_read_only because DeviceConfig may not be available when Build.VERSION_CODES is first accessed is_fixed_read_only: true diff --git a/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java b/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java index 8d2d04471f8e..8160ca2eba69 100644 --- a/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java +++ b/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java @@ -159,9 +159,7 @@ public class LongArrayMultiStateCounterPerfTest { LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4); final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); long time = 1000; - LongArrayMultiStateCounter.LongArrayContainer timeInFreq = - new LongArrayMultiStateCounter.LongArrayContainer(4); - timeInFreq.setValues(new long[]{100, 200, 300, 400}); + long[] timeInFreq = {100, 200, 300, 400}; while (state.keepRunning()) { counter.setState(1, time); counter.setState(0, time + 1000); 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/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/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 9bcdcd14aad0..1a53437b3364 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1074,6 +1074,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 +8024,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 +9247,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 +9257,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 @@ -16416,6 +16420,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 @@ -18706,6 +18711,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 @@ -20984,6 +20990,7 @@ package android.inputmethodservice { method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface(); method public android.view.View onCreateInputView(); method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public void onCustomImeSwitcherButtonRequestedVisible(boolean); method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]); method public boolean onEvaluateFullscreenMode(); method @CallSuper public boolean onEvaluateInputViewShown(); @@ -52995,7 +53002,7 @@ package android.view { method public void addOnUnhandledKeyEventListener(android.view.View.OnUnhandledKeyEventListener); method public void addTouchables(java.util.ArrayList<android.view.View>); method public android.view.ViewPropertyAnimator animate(); - method public void announceForAccessibility(CharSequence); + method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_accessibility_announcement_apis") public void announceForAccessibility(CharSequence); method public void autofill(android.view.autofill.AutofillValue); method public void autofill(@NonNull android.util.SparseArray<android.view.autofill.AutofillValue>); method protected boolean awakenScrollBars(); @@ -55245,7 +55252,7 @@ package android.view.accessibility { field public static final int SPEECH_STATE_SPEAKING_END = 2; // 0x2 field public static final int SPEECH_STATE_SPEAKING_START = 1; // 0x1 field public static final int TYPES_ALL_MASK = -1; // 0xffffffff - field public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000 + field @Deprecated @FlaggedApi("android.view.accessibility.deprecate_accessibility_announcement_apis") public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000 field public static final int TYPE_ASSIST_READING_CONTEXT = 16777216; // 0x1000000 field public static final int TYPE_GESTURE_DETECTION_END = 524288; // 0x80000 field public static final int TYPE_GESTURE_DETECTION_START = 262144; // 0x40000 @@ -56973,6 +56980,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(); @@ -56992,6 +57002,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 02cd00d0f86a..26cb5621bab0 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3558,10 +3558,12 @@ package android.companion.virtual { method @Deprecated public int getDefaultActivityPolicy(); method @Deprecated public int getDefaultNavigationPolicy(); method public int getDevicePolicy(int); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getDimDuration(); method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent(); method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @Nullable public android.content.ComponentName getInputMethodComponent(); method public int getLockState(); method @Nullable public String getName(); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getScreenOffTimeout(); method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts(); method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -3579,6 +3581,7 @@ package android.companion.virtual { field @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6; // 0x6 field @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final int POLICY_TYPE_CAMERA = 5; // 0x5 field @FlaggedApi("android.companion.virtual.flags.cross_device_clipboard") public static final int POLICY_TYPE_CLIPBOARD = 4; // 0x4 + field @FlaggedApi("android.companion.virtualdevice.flags.default_device_camera_access_policy") public static final int POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS = 7; // 0x7 field public static final int POLICY_TYPE_RECENTS = 2; // 0x2 field public static final int POLICY_TYPE_SENSORS = 0; // 0x0 } @@ -3594,10 +3597,12 @@ package android.companion.virtual { method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>); method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDimDuration(@NonNull java.time.Duration); method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName); method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName); method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setScreenOffTimeout(@NonNull java.time.Duration); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setVirtualSensorCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensorCallback); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setVirtualSensorDirectChannelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensorDirectChannelCallback); @@ -5288,13 +5293,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); @@ -7022,7 +7033,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/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/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 2b0e86c3205c..4c860fac9871 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; @@ -910,159 +913,157 @@ public class AppOpsManager { /** @hide No operation specified. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int OP_NONE = AppProtoEnums.APP_OP_NONE; + public static final int OP_NONE = AppOpEnums.APP_OP_NONE; /** @hide Access to coarse location information. */ @UnsupportedAppUsage @TestApi - public static final int OP_COARSE_LOCATION = AppProtoEnums.APP_OP_COARSE_LOCATION; + public static final int OP_COARSE_LOCATION = AppOpEnums.APP_OP_COARSE_LOCATION; /** @hide Access to fine location information. */ @UnsupportedAppUsage - public static final int OP_FINE_LOCATION = AppProtoEnums.APP_OP_FINE_LOCATION; + public static final int OP_FINE_LOCATION = AppOpEnums.APP_OP_FINE_LOCATION; /** @hide Causing GPS to run. */ @UnsupportedAppUsage - public static final int OP_GPS = AppProtoEnums.APP_OP_GPS; + public static final int OP_GPS = AppOpEnums.APP_OP_GPS; /** @hide */ @UnsupportedAppUsage - public static final int OP_VIBRATE = AppProtoEnums.APP_OP_VIBRATE; + public static final int OP_VIBRATE = AppOpEnums.APP_OP_VIBRATE; /** @hide */ @UnsupportedAppUsage - public static final int OP_READ_CONTACTS = AppProtoEnums.APP_OP_READ_CONTACTS; + public static final int OP_READ_CONTACTS = AppOpEnums.APP_OP_READ_CONTACTS; /** @hide */ @UnsupportedAppUsage - public static final int OP_WRITE_CONTACTS = AppProtoEnums.APP_OP_WRITE_CONTACTS; + public static final int OP_WRITE_CONTACTS = AppOpEnums.APP_OP_WRITE_CONTACTS; /** @hide */ @UnsupportedAppUsage - public static final int OP_READ_CALL_LOG = AppProtoEnums.APP_OP_READ_CALL_LOG; + public static final int OP_READ_CALL_LOG = AppOpEnums.APP_OP_READ_CALL_LOG; /** @hide */ @UnsupportedAppUsage - public static final int OP_WRITE_CALL_LOG = AppProtoEnums.APP_OP_WRITE_CALL_LOG; + public static final int OP_WRITE_CALL_LOG = AppOpEnums.APP_OP_WRITE_CALL_LOG; /** @hide */ @UnsupportedAppUsage - public static final int OP_READ_CALENDAR = AppProtoEnums.APP_OP_READ_CALENDAR; + public static final int OP_READ_CALENDAR = AppOpEnums.APP_OP_READ_CALENDAR; /** @hide */ @UnsupportedAppUsage - public static final int OP_WRITE_CALENDAR = AppProtoEnums.APP_OP_WRITE_CALENDAR; + public static final int OP_WRITE_CALENDAR = AppOpEnums.APP_OP_WRITE_CALENDAR; /** @hide */ @UnsupportedAppUsage - public static final int OP_WIFI_SCAN = AppProtoEnums.APP_OP_WIFI_SCAN; + public static final int OP_WIFI_SCAN = AppOpEnums.APP_OP_WIFI_SCAN; /** @hide */ @UnsupportedAppUsage - public static final int OP_POST_NOTIFICATION = AppProtoEnums.APP_OP_POST_NOTIFICATION; + public static final int OP_POST_NOTIFICATION = AppOpEnums.APP_OP_POST_NOTIFICATION; /** @hide */ @UnsupportedAppUsage - public static final int OP_NEIGHBORING_CELLS = AppProtoEnums.APP_OP_NEIGHBORING_CELLS; + public static final int OP_NEIGHBORING_CELLS = AppOpEnums.APP_OP_NEIGHBORING_CELLS; /** @hide */ @UnsupportedAppUsage - public static final int OP_CALL_PHONE = AppProtoEnums.APP_OP_CALL_PHONE; + public static final int OP_CALL_PHONE = AppOpEnums.APP_OP_CALL_PHONE; /** @hide */ @UnsupportedAppUsage - public static final int OP_READ_SMS = AppProtoEnums.APP_OP_READ_SMS; + public static final int OP_READ_SMS = AppOpEnums.APP_OP_READ_SMS; /** @hide */ @UnsupportedAppUsage - public static final int OP_WRITE_SMS = AppProtoEnums.APP_OP_WRITE_SMS; + public static final int OP_WRITE_SMS = AppOpEnums.APP_OP_WRITE_SMS; /** @hide */ @UnsupportedAppUsage - public static final int OP_RECEIVE_SMS = AppProtoEnums.APP_OP_RECEIVE_SMS; + public static final int OP_RECEIVE_SMS = AppOpEnums.APP_OP_RECEIVE_SMS; /** @hide */ @UnsupportedAppUsage - public static final int OP_RECEIVE_EMERGECY_SMS = - AppProtoEnums.APP_OP_RECEIVE_EMERGENCY_SMS; + public static final int OP_RECEIVE_EMERGECY_SMS = AppOpEnums.APP_OP_RECEIVE_EMERGENCY_SMS; /** @hide */ @UnsupportedAppUsage - public static final int OP_RECEIVE_MMS = AppProtoEnums.APP_OP_RECEIVE_MMS; + public static final int OP_RECEIVE_MMS = AppOpEnums.APP_OP_RECEIVE_MMS; /** @hide */ @UnsupportedAppUsage - public static final int OP_RECEIVE_WAP_PUSH = AppProtoEnums.APP_OP_RECEIVE_WAP_PUSH; + public static final int OP_RECEIVE_WAP_PUSH = AppOpEnums.APP_OP_RECEIVE_WAP_PUSH; /** @hide */ @UnsupportedAppUsage - public static final int OP_SEND_SMS = AppProtoEnums.APP_OP_SEND_SMS; + public static final int OP_SEND_SMS = AppOpEnums.APP_OP_SEND_SMS; /** @hide */ - public static final int OP_MANAGE_ONGOING_CALLS = AppProtoEnums.APP_OP_MANAGE_ONGOING_CALLS; + public static final int OP_MANAGE_ONGOING_CALLS = AppOpEnums.APP_OP_MANAGE_ONGOING_CALLS; /** @hide */ @UnsupportedAppUsage - public static final int OP_READ_ICC_SMS = AppProtoEnums.APP_OP_READ_ICC_SMS; + public static final int OP_READ_ICC_SMS = AppOpEnums.APP_OP_READ_ICC_SMS; /** @hide */ @UnsupportedAppUsage - public static final int OP_WRITE_ICC_SMS = AppProtoEnums.APP_OP_WRITE_ICC_SMS; + public static final int OP_WRITE_ICC_SMS = AppOpEnums.APP_OP_WRITE_ICC_SMS; /** @hide */ @UnsupportedAppUsage - public static final int OP_WRITE_SETTINGS = AppProtoEnums.APP_OP_WRITE_SETTINGS; + public static final int OP_WRITE_SETTINGS = AppOpEnums.APP_OP_WRITE_SETTINGS; /** @hide Required to draw on top of other apps. */ @UnsupportedAppUsage @TestApi - public static final int OP_SYSTEM_ALERT_WINDOW = AppProtoEnums.APP_OP_SYSTEM_ALERT_WINDOW; + public static final int OP_SYSTEM_ALERT_WINDOW = AppOpEnums.APP_OP_SYSTEM_ALERT_WINDOW; /** @hide */ @UnsupportedAppUsage - public static final int OP_ACCESS_NOTIFICATIONS = - AppProtoEnums.APP_OP_ACCESS_NOTIFICATIONS; + public static final int OP_ACCESS_NOTIFICATIONS = AppOpEnums.APP_OP_ACCESS_NOTIFICATIONS; /** @hide */ @UnsupportedAppUsage - public static final int OP_CAMERA = AppProtoEnums.APP_OP_CAMERA; + public static final int OP_CAMERA = AppOpEnums.APP_OP_CAMERA; /** @hide */ @UnsupportedAppUsage @TestApi - public static final int OP_RECORD_AUDIO = AppProtoEnums.APP_OP_RECORD_AUDIO; + public static final int OP_RECORD_AUDIO = AppOpEnums.APP_OP_RECORD_AUDIO; /** @hide */ @UnsupportedAppUsage - public static final int OP_PLAY_AUDIO = AppProtoEnums.APP_OP_PLAY_AUDIO; + public static final int OP_PLAY_AUDIO = AppOpEnums.APP_OP_PLAY_AUDIO; /** @hide */ @UnsupportedAppUsage - public static final int OP_READ_CLIPBOARD = AppProtoEnums.APP_OP_READ_CLIPBOARD; + public static final int OP_READ_CLIPBOARD = AppOpEnums.APP_OP_READ_CLIPBOARD; /** @hide */ @UnsupportedAppUsage - public static final int OP_WRITE_CLIPBOARD = AppProtoEnums.APP_OP_WRITE_CLIPBOARD; + public static final int OP_WRITE_CLIPBOARD = AppOpEnums.APP_OP_WRITE_CLIPBOARD; /** @hide */ @UnsupportedAppUsage - public static final int OP_TAKE_MEDIA_BUTTONS = AppProtoEnums.APP_OP_TAKE_MEDIA_BUTTONS; + public static final int OP_TAKE_MEDIA_BUTTONS = AppOpEnums.APP_OP_TAKE_MEDIA_BUTTONS; /** @hide */ @UnsupportedAppUsage - public static final int OP_TAKE_AUDIO_FOCUS = AppProtoEnums.APP_OP_TAKE_AUDIO_FOCUS; + public static final int OP_TAKE_AUDIO_FOCUS = AppOpEnums.APP_OP_TAKE_AUDIO_FOCUS; /** @hide */ @UnsupportedAppUsage - public static final int OP_AUDIO_MASTER_VOLUME = AppProtoEnums.APP_OP_AUDIO_MASTER_VOLUME; + public static final int OP_AUDIO_MASTER_VOLUME = AppOpEnums.APP_OP_AUDIO_MASTER_VOLUME; /** @hide */ @UnsupportedAppUsage - public static final int OP_AUDIO_VOICE_VOLUME = AppProtoEnums.APP_OP_AUDIO_VOICE_VOLUME; + public static final int OP_AUDIO_VOICE_VOLUME = AppOpEnums.APP_OP_AUDIO_VOICE_VOLUME; /** @hide */ @UnsupportedAppUsage - public static final int OP_AUDIO_RING_VOLUME = AppProtoEnums.APP_OP_AUDIO_RING_VOLUME; + public static final int OP_AUDIO_RING_VOLUME = AppOpEnums.APP_OP_AUDIO_RING_VOLUME; /** @hide */ @UnsupportedAppUsage - public static final int OP_AUDIO_MEDIA_VOLUME = AppProtoEnums.APP_OP_AUDIO_MEDIA_VOLUME; + public static final int OP_AUDIO_MEDIA_VOLUME = AppOpEnums.APP_OP_AUDIO_MEDIA_VOLUME; /** @hide */ @UnsupportedAppUsage - public static final int OP_AUDIO_ALARM_VOLUME = AppProtoEnums.APP_OP_AUDIO_ALARM_VOLUME; + public static final int OP_AUDIO_ALARM_VOLUME = AppOpEnums.APP_OP_AUDIO_ALARM_VOLUME; /** @hide */ @UnsupportedAppUsage public static final int OP_AUDIO_NOTIFICATION_VOLUME = - AppProtoEnums.APP_OP_AUDIO_NOTIFICATION_VOLUME; + AppOpEnums.APP_OP_AUDIO_NOTIFICATION_VOLUME; /** @hide */ @UnsupportedAppUsage public static final int OP_AUDIO_BLUETOOTH_VOLUME = - AppProtoEnums.APP_OP_AUDIO_BLUETOOTH_VOLUME; + AppOpEnums.APP_OP_AUDIO_BLUETOOTH_VOLUME; /** @hide */ @UnsupportedAppUsage - public static final int OP_WAKE_LOCK = AppProtoEnums.APP_OP_WAKE_LOCK; + public static final int OP_WAKE_LOCK = AppOpEnums.APP_OP_WAKE_LOCK; /** @hide Continually monitoring location data. */ @UnsupportedAppUsage public static final int OP_MONITOR_LOCATION = - AppProtoEnums.APP_OP_MONITOR_LOCATION; + AppOpEnums.APP_OP_MONITOR_LOCATION; /** @hide Continually monitoring location data with a relatively high power request. */ @UnsupportedAppUsage public static final int OP_MONITOR_HIGH_POWER_LOCATION = - AppProtoEnums.APP_OP_MONITOR_HIGH_POWER_LOCATION; + AppOpEnums.APP_OP_MONITOR_HIGH_POWER_LOCATION; /** @hide Retrieve current usage stats via {@link UsageStatsManager}. */ @UnsupportedAppUsage - public static final int OP_GET_USAGE_STATS = AppProtoEnums.APP_OP_GET_USAGE_STATS; + public static final int OP_GET_USAGE_STATS = AppOpEnums.APP_OP_GET_USAGE_STATS; /** @hide */ @UnsupportedAppUsage - public static final int OP_MUTE_MICROPHONE = AppProtoEnums.APP_OP_MUTE_MICROPHONE; + public static final int OP_MUTE_MICROPHONE = AppOpEnums.APP_OP_MUTE_MICROPHONE; /** @hide */ @UnsupportedAppUsage - public static final int OP_TOAST_WINDOW = AppProtoEnums.APP_OP_TOAST_WINDOW; + public static final int OP_TOAST_WINDOW = AppOpEnums.APP_OP_TOAST_WINDOW; /** @hide Capture the device's display contents and/or audio */ @UnsupportedAppUsage - public static final int OP_PROJECT_MEDIA = AppProtoEnums.APP_OP_PROJECT_MEDIA; + public static final int OP_PROJECT_MEDIA = AppOpEnums.APP_OP_PROJECT_MEDIA; /** * Start (without additional user intervention) a VPN connection, as used by {@link * android.net.VpnService} along with as Platform VPN connections, as used by {@link @@ -1075,146 +1076,141 @@ public class AppOpsManager { * @hide */ @UnsupportedAppUsage - public static final int OP_ACTIVATE_VPN = AppProtoEnums.APP_OP_ACTIVATE_VPN; + public static final int OP_ACTIVATE_VPN = AppOpEnums.APP_OP_ACTIVATE_VPN; /** @hide Access the WallpaperManagerAPI to write wallpapers. */ @UnsupportedAppUsage - public static final int OP_WRITE_WALLPAPER = AppProtoEnums.APP_OP_WRITE_WALLPAPER; + public static final int OP_WRITE_WALLPAPER = AppOpEnums.APP_OP_WRITE_WALLPAPER; /** @hide Received the assist structure from an app. */ @UnsupportedAppUsage - public static final int OP_ASSIST_STRUCTURE = AppProtoEnums.APP_OP_ASSIST_STRUCTURE; + public static final int OP_ASSIST_STRUCTURE = AppOpEnums.APP_OP_ASSIST_STRUCTURE; /** @hide Received a screenshot from assist. */ @UnsupportedAppUsage - public static final int OP_ASSIST_SCREENSHOT = AppProtoEnums.APP_OP_ASSIST_SCREENSHOT; + public static final int OP_ASSIST_SCREENSHOT = AppOpEnums.APP_OP_ASSIST_SCREENSHOT; /** @hide Read the phone state. */ @UnsupportedAppUsage - public static final int OP_READ_PHONE_STATE = AppProtoEnums.APP_OP_READ_PHONE_STATE; + public static final int OP_READ_PHONE_STATE = AppOpEnums.APP_OP_READ_PHONE_STATE; /** @hide Add voicemail messages to the voicemail content provider. */ @UnsupportedAppUsage - public static final int OP_ADD_VOICEMAIL = AppProtoEnums.APP_OP_ADD_VOICEMAIL; + public static final int OP_ADD_VOICEMAIL = AppOpEnums.APP_OP_ADD_VOICEMAIL; /** @hide Access APIs for SIP calling over VOIP or WiFi. */ @UnsupportedAppUsage - public static final int OP_USE_SIP = AppProtoEnums.APP_OP_USE_SIP; + public static final int OP_USE_SIP = AppOpEnums.APP_OP_USE_SIP; /** @hide Intercept outgoing calls. */ @UnsupportedAppUsage - public static final int OP_PROCESS_OUTGOING_CALLS = - AppProtoEnums.APP_OP_PROCESS_OUTGOING_CALLS; + public static final int OP_PROCESS_OUTGOING_CALLS = AppOpEnums.APP_OP_PROCESS_OUTGOING_CALLS; /** @hide User the fingerprint API. */ @UnsupportedAppUsage - public static final int OP_USE_FINGERPRINT = AppProtoEnums.APP_OP_USE_FINGERPRINT; + public static final int OP_USE_FINGERPRINT = AppOpEnums.APP_OP_USE_FINGERPRINT; /** @hide Access to body sensors such as heart rate, etc. */ @UnsupportedAppUsage - public static final int OP_BODY_SENSORS = AppProtoEnums.APP_OP_BODY_SENSORS; + public static final int OP_BODY_SENSORS = AppOpEnums.APP_OP_BODY_SENSORS; /** @hide Read previously received cell broadcast messages. */ @UnsupportedAppUsage - public static final int OP_READ_CELL_BROADCASTS = AppProtoEnums.APP_OP_READ_CELL_BROADCASTS; + public static final int OP_READ_CELL_BROADCASTS = AppOpEnums.APP_OP_READ_CELL_BROADCASTS; /** @hide Inject mock location into the system. */ @UnsupportedAppUsage - public static final int OP_MOCK_LOCATION = AppProtoEnums.APP_OP_MOCK_LOCATION; + public static final int OP_MOCK_LOCATION = AppOpEnums.APP_OP_MOCK_LOCATION; /** @hide Read external storage. */ @UnsupportedAppUsage - public static final int OP_READ_EXTERNAL_STORAGE = AppProtoEnums.APP_OP_READ_EXTERNAL_STORAGE; + public static final int OP_READ_EXTERNAL_STORAGE = AppOpEnums.APP_OP_READ_EXTERNAL_STORAGE; /** @hide Write external storage. */ @UnsupportedAppUsage - public static final int OP_WRITE_EXTERNAL_STORAGE = - AppProtoEnums.APP_OP_WRITE_EXTERNAL_STORAGE; + public static final int OP_WRITE_EXTERNAL_STORAGE = AppOpEnums.APP_OP_WRITE_EXTERNAL_STORAGE; /** @hide Turned on the screen. */ @UnsupportedAppUsage - public static final int OP_TURN_SCREEN_ON = AppProtoEnums.APP_OP_TURN_SCREEN_ON; + public static final int OP_TURN_SCREEN_ON = AppOpEnums.APP_OP_TURN_SCREEN_ON; /** @hide Get device accounts. */ @UnsupportedAppUsage - public static final int OP_GET_ACCOUNTS = AppProtoEnums.APP_OP_GET_ACCOUNTS; + public static final int OP_GET_ACCOUNTS = AppOpEnums.APP_OP_GET_ACCOUNTS; /** @hide Control whether an application is allowed to run in the background. */ @UnsupportedAppUsage - public static final int OP_RUN_IN_BACKGROUND = - AppProtoEnums.APP_OP_RUN_IN_BACKGROUND; + public static final int OP_RUN_IN_BACKGROUND = AppOpEnums.APP_OP_RUN_IN_BACKGROUND; /** @hide */ @UnsupportedAppUsage public static final int OP_AUDIO_ACCESSIBILITY_VOLUME = - AppProtoEnums.APP_OP_AUDIO_ACCESSIBILITY_VOLUME; + AppOpEnums.APP_OP_AUDIO_ACCESSIBILITY_VOLUME; /** @hide Read the phone number. */ @UnsupportedAppUsage - public static final int OP_READ_PHONE_NUMBERS = AppProtoEnums.APP_OP_READ_PHONE_NUMBERS; + public static final int OP_READ_PHONE_NUMBERS = AppOpEnums.APP_OP_READ_PHONE_NUMBERS; /** @hide Request package installs through package installer */ @UnsupportedAppUsage public static final int OP_REQUEST_INSTALL_PACKAGES = - AppProtoEnums.APP_OP_REQUEST_INSTALL_PACKAGES; + AppOpEnums.APP_OP_REQUEST_INSTALL_PACKAGES; /** @hide Enter picture-in-picture. */ @UnsupportedAppUsage - public static final int OP_PICTURE_IN_PICTURE = AppProtoEnums.APP_OP_PICTURE_IN_PICTURE; + public static final int OP_PICTURE_IN_PICTURE = AppOpEnums.APP_OP_PICTURE_IN_PICTURE; /** @hide Instant app start foreground service. */ @UnsupportedAppUsage public static final int OP_INSTANT_APP_START_FOREGROUND = - AppProtoEnums.APP_OP_INSTANT_APP_START_FOREGROUND; + AppOpEnums.APP_OP_INSTANT_APP_START_FOREGROUND; /** @hide Answer incoming phone calls */ @UnsupportedAppUsage - public static final int OP_ANSWER_PHONE_CALLS = AppProtoEnums.APP_OP_ANSWER_PHONE_CALLS; + public static final int OP_ANSWER_PHONE_CALLS = AppOpEnums.APP_OP_ANSWER_PHONE_CALLS; /** @hide Run jobs when in background */ @UnsupportedAppUsage - public static final int OP_RUN_ANY_IN_BACKGROUND = AppProtoEnums.APP_OP_RUN_ANY_IN_BACKGROUND; + public static final int OP_RUN_ANY_IN_BACKGROUND = AppOpEnums.APP_OP_RUN_ANY_IN_BACKGROUND; /** @hide Change Wi-Fi connectivity state */ @UnsupportedAppUsage - public static final int OP_CHANGE_WIFI_STATE = AppProtoEnums.APP_OP_CHANGE_WIFI_STATE; + public static final int OP_CHANGE_WIFI_STATE = AppOpEnums.APP_OP_CHANGE_WIFI_STATE; /** @hide Request package deletion through package installer */ @UnsupportedAppUsage - public static final int OP_REQUEST_DELETE_PACKAGES = - AppProtoEnums.APP_OP_REQUEST_DELETE_PACKAGES; + public static final int OP_REQUEST_DELETE_PACKAGES = AppOpEnums.APP_OP_REQUEST_DELETE_PACKAGES; /** @hide Bind an accessibility service. */ @UnsupportedAppUsage public static final int OP_BIND_ACCESSIBILITY_SERVICE = - AppProtoEnums.APP_OP_BIND_ACCESSIBILITY_SERVICE; + AppOpEnums.APP_OP_BIND_ACCESSIBILITY_SERVICE; /** @hide Continue handover of a call from another app */ @UnsupportedAppUsage - public static final int OP_ACCEPT_HANDOVER = AppProtoEnums.APP_OP_ACCEPT_HANDOVER; + public static final int OP_ACCEPT_HANDOVER = AppOpEnums.APP_OP_ACCEPT_HANDOVER; /** @hide Create and Manage IPsec Tunnels */ @UnsupportedAppUsage - public static final int OP_MANAGE_IPSEC_TUNNELS = AppProtoEnums.APP_OP_MANAGE_IPSEC_TUNNELS; + public static final int OP_MANAGE_IPSEC_TUNNELS = AppOpEnums.APP_OP_MANAGE_IPSEC_TUNNELS; /** @hide Any app start foreground service. */ @UnsupportedAppUsage @TestApi - public static final int OP_START_FOREGROUND = AppProtoEnums.APP_OP_START_FOREGROUND; + public static final int OP_START_FOREGROUND = AppOpEnums.APP_OP_START_FOREGROUND; /** @hide */ @UnsupportedAppUsage - public static final int OP_BLUETOOTH_SCAN = AppProtoEnums.APP_OP_BLUETOOTH_SCAN; + public static final int OP_BLUETOOTH_SCAN = AppOpEnums.APP_OP_BLUETOOTH_SCAN; /** @hide */ - public static final int OP_BLUETOOTH_CONNECT = AppProtoEnums.APP_OP_BLUETOOTH_CONNECT; + public static final int OP_BLUETOOTH_CONNECT = AppOpEnums.APP_OP_BLUETOOTH_CONNECT; /** @hide */ - public static final int OP_BLUETOOTH_ADVERTISE = AppProtoEnums.APP_OP_BLUETOOTH_ADVERTISE; + public static final int OP_BLUETOOTH_ADVERTISE = AppOpEnums.APP_OP_BLUETOOTH_ADVERTISE; /** @hide Use the BiometricPrompt/BiometricManager APIs. */ - public static final int OP_USE_BIOMETRIC = AppProtoEnums.APP_OP_USE_BIOMETRIC; + public static final int OP_USE_BIOMETRIC = AppOpEnums.APP_OP_USE_BIOMETRIC; /** @hide Physical activity recognition. */ - public static final int OP_ACTIVITY_RECOGNITION = AppProtoEnums.APP_OP_ACTIVITY_RECOGNITION; + public static final int OP_ACTIVITY_RECOGNITION = AppOpEnums.APP_OP_ACTIVITY_RECOGNITION; /** @hide Financial app sms read. */ public static final int OP_SMS_FINANCIAL_TRANSACTIONS = - AppProtoEnums.APP_OP_SMS_FINANCIAL_TRANSACTIONS; + AppOpEnums.APP_OP_SMS_FINANCIAL_TRANSACTIONS; /** @hide Read media of audio type. */ - public static final int OP_READ_MEDIA_AUDIO = AppProtoEnums.APP_OP_READ_MEDIA_AUDIO; + public static final int OP_READ_MEDIA_AUDIO = AppOpEnums.APP_OP_READ_MEDIA_AUDIO; /** @hide Write media of audio type. */ - public static final int OP_WRITE_MEDIA_AUDIO = AppProtoEnums.APP_OP_WRITE_MEDIA_AUDIO; + public static final int OP_WRITE_MEDIA_AUDIO = AppOpEnums.APP_OP_WRITE_MEDIA_AUDIO; /** @hide Read media of video type. */ - public static final int OP_READ_MEDIA_VIDEO = AppProtoEnums.APP_OP_READ_MEDIA_VIDEO; + public static final int OP_READ_MEDIA_VIDEO = AppOpEnums.APP_OP_READ_MEDIA_VIDEO; /** @hide Write media of video type. */ - public static final int OP_WRITE_MEDIA_VIDEO = AppProtoEnums.APP_OP_WRITE_MEDIA_VIDEO; + public static final int OP_WRITE_MEDIA_VIDEO = AppOpEnums.APP_OP_WRITE_MEDIA_VIDEO; /** @hide Read media of image type. */ - public static final int OP_READ_MEDIA_IMAGES = AppProtoEnums.APP_OP_READ_MEDIA_IMAGES; + public static final int OP_READ_MEDIA_IMAGES = AppOpEnums.APP_OP_READ_MEDIA_IMAGES; /** @hide Write media of image type. */ - public static final int OP_WRITE_MEDIA_IMAGES = AppProtoEnums.APP_OP_WRITE_MEDIA_IMAGES; + public static final int OP_WRITE_MEDIA_IMAGES = AppOpEnums.APP_OP_WRITE_MEDIA_IMAGES; /** @hide Has a legacy (non-isolated) view of storage. */ - public static final int OP_LEGACY_STORAGE = AppProtoEnums.APP_OP_LEGACY_STORAGE; + public static final int OP_LEGACY_STORAGE = AppOpEnums.APP_OP_LEGACY_STORAGE; /** @hide Accessing accessibility features */ - public static final int OP_ACCESS_ACCESSIBILITY = AppProtoEnums.APP_OP_ACCESS_ACCESSIBILITY; + public static final int OP_ACCESS_ACCESSIBILITY = AppOpEnums.APP_OP_ACCESS_ACCESSIBILITY; /** @hide Read the device identifiers (IMEI / MEID, IMSI, SIM / Build serial) */ public static final int OP_READ_DEVICE_IDENTIFIERS = - AppProtoEnums.APP_OP_READ_DEVICE_IDENTIFIERS; + AppOpEnums.APP_OP_READ_DEVICE_IDENTIFIERS; /** @hide Read location metadata from media */ - public static final int OP_ACCESS_MEDIA_LOCATION = AppProtoEnums.APP_OP_ACCESS_MEDIA_LOCATION; + public static final int OP_ACCESS_MEDIA_LOCATION = AppOpEnums.APP_OP_ACCESS_MEDIA_LOCATION; /** @hide Query all apps on device, regardless of declarations in the calling app manifest */ - public static final int OP_QUERY_ALL_PACKAGES = AppProtoEnums.APP_OP_QUERY_ALL_PACKAGES; + public static final int OP_QUERY_ALL_PACKAGES = AppOpEnums.APP_OP_QUERY_ALL_PACKAGES; /** @hide Access all external storage */ - public static final int OP_MANAGE_EXTERNAL_STORAGE = - AppProtoEnums.APP_OP_MANAGE_EXTERNAL_STORAGE; + public static final int OP_MANAGE_EXTERNAL_STORAGE = AppOpEnums.APP_OP_MANAGE_EXTERNAL_STORAGE; /** @hide Communicate cross-profile within the same profile group. */ public static final int OP_INTERACT_ACROSS_PROFILES = - AppProtoEnums.APP_OP_INTERACT_ACROSS_PROFILES; + AppOpEnums.APP_OP_INTERACT_ACROSS_PROFILES; /** * Start (without additional user intervention) a Platform VPN connection, as used by {@link * android.net.VpnManager} @@ -1225,16 +1221,16 @@ public class AppOpsManager { * * @hide */ - public static final int OP_ACTIVATE_PLATFORM_VPN = AppProtoEnums.APP_OP_ACTIVATE_PLATFORM_VPN; + public static final int OP_ACTIVATE_PLATFORM_VPN = AppOpEnums.APP_OP_ACTIVATE_PLATFORM_VPN; /** @hide Controls whether or not read logs are available for incremental installations. */ - public static final int OP_LOADER_USAGE_STATS = AppProtoEnums.APP_OP_LOADER_USAGE_STATS; + public static final int OP_LOADER_USAGE_STATS = AppOpEnums.APP_OP_LOADER_USAGE_STATS; // App op deprecated/removed. - private static final int OP_DEPRECATED_1 = AppProtoEnums.APP_OP_DEPRECATED_1; + private static final int OP_DEPRECATED_1 = AppOpEnums.APP_OP_DEPRECATED_1; /** @hide Auto-revoke app permissions if app is unused for an extended period */ public static final int OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED = - AppProtoEnums.APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED; + AppOpEnums.APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED; /** * Whether {@link #OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED} is allowed to be changed by @@ -1243,55 +1239,55 @@ public class AppOpsManager { * @hide */ public static final int OP_AUTO_REVOKE_MANAGED_BY_INSTALLER = - AppProtoEnums.APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER; + AppOpEnums.APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER; /** @hide */ - public static final int OP_NO_ISOLATED_STORAGE = AppProtoEnums.APP_OP_NO_ISOLATED_STORAGE; + public static final int OP_NO_ISOLATED_STORAGE = AppOpEnums.APP_OP_NO_ISOLATED_STORAGE; /** * Phone call is using microphone * * @hide */ - public static final int OP_PHONE_CALL_MICROPHONE = AppProtoEnums.APP_OP_PHONE_CALL_MICROPHONE; + public static final int OP_PHONE_CALL_MICROPHONE = AppOpEnums.APP_OP_PHONE_CALL_MICROPHONE; /** * Phone call is using camera * * @hide */ - public static final int OP_PHONE_CALL_CAMERA = AppProtoEnums.APP_OP_PHONE_CALL_CAMERA; + public static final int OP_PHONE_CALL_CAMERA = AppOpEnums.APP_OP_PHONE_CALL_CAMERA; /** * Audio is being recorded for hotword detection. * * @hide */ - public static final int OP_RECORD_AUDIO_HOTWORD = AppProtoEnums.APP_OP_RECORD_AUDIO_HOTWORD; + public static final int OP_RECORD_AUDIO_HOTWORD = AppOpEnums.APP_OP_RECORD_AUDIO_HOTWORD; /** * Manage credentials in the system KeyChain. * * @hide */ - public static final int OP_MANAGE_CREDENTIALS = AppProtoEnums.APP_OP_MANAGE_CREDENTIALS; + public static final int OP_MANAGE_CREDENTIALS = AppOpEnums.APP_OP_MANAGE_CREDENTIALS; /** @hide */ public static final int OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER = - AppProtoEnums.APP_OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER; + AppOpEnums.APP_OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER; /** * App output audio is being recorded * * @hide */ - public static final int OP_RECORD_AUDIO_OUTPUT = AppProtoEnums.APP_OP_RECORD_AUDIO_OUTPUT; + public static final int OP_RECORD_AUDIO_OUTPUT = AppOpEnums.APP_OP_RECORD_AUDIO_OUTPUT; /** * App can schedule exact alarm to perform timing based background work * * @hide */ - public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM; + public static final int OP_SCHEDULE_EXACT_ALARM = AppOpEnums.APP_OP_SCHEDULE_EXACT_ALARM; /** * Fine location being accessed by a location source, which is @@ -1301,7 +1297,7 @@ public class AppOpsManager { * * @hide */ - public static final int OP_FINE_LOCATION_SOURCE = AppProtoEnums.APP_OP_FINE_LOCATION_SOURCE; + public static final int OP_FINE_LOCATION_SOURCE = AppOpEnums.APP_OP_FINE_LOCATION_SOURCE; /** * Coarse location being accessed by a location source, which is @@ -1311,7 +1307,7 @@ public class AppOpsManager { * * @hide */ - public static final int OP_COARSE_LOCATION_SOURCE = AppProtoEnums.APP_OP_COARSE_LOCATION_SOURCE; + public static final int OP_COARSE_LOCATION_SOURCE = AppOpEnums.APP_OP_COARSE_LOCATION_SOURCE; /** * Allow apps to create the requests to manage the media files without user confirmation. @@ -1323,13 +1319,13 @@ public class AppOpsManager { * * @hide */ - public static final int OP_MANAGE_MEDIA = AppProtoEnums.APP_OP_MANAGE_MEDIA; + public static final int OP_MANAGE_MEDIA = AppOpEnums.APP_OP_MANAGE_MEDIA; /** @hide */ - public static final int OP_UWB_RANGING = AppProtoEnums.APP_OP_UWB_RANGING; + public static final int OP_UWB_RANGING = AppOpEnums.APP_OP_UWB_RANGING; /** @hide */ - public static final int OP_NEARBY_WIFI_DEVICES = AppProtoEnums.APP_OP_NEARBY_WIFI_DEVICES; + public static final int OP_NEARBY_WIFI_DEVICES = AppOpEnums.APP_OP_NEARBY_WIFI_DEVICES; /** * Activity recognition being accessed by an activity recognition source, which @@ -1339,7 +1335,7 @@ public class AppOpsManager { * @hide */ public static final int OP_ACTIVITY_RECOGNITION_SOURCE = - AppProtoEnums.APP_OP_ACTIVITY_RECOGNITION_SOURCE; + AppOpEnums.APP_OP_ACTIVITY_RECOGNITION_SOURCE; /** * Incoming phone audio is being recorded @@ -1347,21 +1343,21 @@ public class AppOpsManager { * @hide */ public static final int OP_RECORD_INCOMING_PHONE_AUDIO = - AppProtoEnums.APP_OP_RECORD_INCOMING_PHONE_AUDIO; + AppOpEnums.APP_OP_RECORD_INCOMING_PHONE_AUDIO; /** * VPN app establishes a connection through the VpnService API. * * @hide */ - public static final int OP_ESTABLISH_VPN_SERVICE = AppProtoEnums.APP_OP_ESTABLISH_VPN_SERVICE; + public static final int OP_ESTABLISH_VPN_SERVICE = AppOpEnums.APP_OP_ESTABLISH_VPN_SERVICE; /** * VPN app establishes a connection through the VpnManager API. * * @hide */ - public static final int OP_ESTABLISH_VPN_MANAGER = AppProtoEnums.APP_OP_ESTABLISH_VPN_MANAGER; + public static final int OP_ESTABLISH_VPN_MANAGER = AppOpEnums.APP_OP_ESTABLISH_VPN_MANAGER; /** * Access restricted settings. @@ -1369,7 +1365,7 @@ public class AppOpsManager { * @hide */ public static final int OP_ACCESS_RESTRICTED_SETTINGS = - AppProtoEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS; + AppOpEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS; /** * Receive microphone audio from an ambient sound detection event @@ -1377,7 +1373,7 @@ public class AppOpsManager { * @hide */ public static final int OP_RECEIVE_AMBIENT_TRIGGER_AUDIO = - AppProtoEnums.APP_OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; + AppOpEnums.APP_OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; /** * Receive audio from near-field mic (ie. TV remote) @@ -1387,15 +1383,14 @@ public class AppOpsManager { * @hide */ public static final int OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = - AppProtoEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO; + AppOpEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO; /** * App can schedule user-initiated jobs. * * @hide */ - public static final int OP_RUN_USER_INITIATED_JOBS = - AppProtoEnums.APP_OP_RUN_USER_INITIATED_JOBS; + public static final int OP_RUN_USER_INITIATED_JOBS = AppOpEnums.APP_OP_RUN_USER_INITIATED_JOBS; /** * Notify apps that they have been granted URI permission photos @@ -1403,7 +1398,7 @@ public class AppOpsManager { * @hide */ public static final int OP_READ_MEDIA_VISUAL_USER_SELECTED = - AppProtoEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED; + AppOpEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED; /** * Prevent an app from being suspended. @@ -1413,7 +1408,7 @@ public class AppOpsManager { * @hide */ public static final int OP_SYSTEM_EXEMPT_FROM_SUSPENSION = - AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_SUSPENSION; + AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_SUSPENSION; /** * Prevent an app from dismissible notifications. Starting from Android U, notifications with @@ -1425,14 +1420,14 @@ public class AppOpsManager { * @hide */ public static final int OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS = - AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS; + AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS; /** * An app op for reading/writing health connect data. * * @hide */ - public static final int OP_READ_WRITE_HEALTH_DATA = AppProtoEnums.APP_OP_READ_WRITE_HEALTH_DATA; + public static final int OP_READ_WRITE_HEALTH_DATA = AppOpEnums.APP_OP_READ_WRITE_HEALTH_DATA; /** * Use foreground service with the type @@ -1441,7 +1436,7 @@ public class AppOpsManager { * @hide */ public static final int OP_FOREGROUND_SERVICE_SPECIAL_USE = - AppProtoEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE; + AppOpEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE; /** * Exempt an app from all power-related restrictions, including app standby and doze. @@ -1453,7 +1448,7 @@ public class AppOpsManager { * @hide */ public static final int OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS = - AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS; + AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS; /** * Prevent an app from being placed into hibernation. @@ -1463,7 +1458,7 @@ public class AppOpsManager { * @hide */ public static final int OP_SYSTEM_EXEMPT_FROM_HIBERNATION = - AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_HIBERNATION; + AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_HIBERNATION; /** * Allows an application to start an activity while running in the background. @@ -1473,7 +1468,7 @@ public class AppOpsManager { * @hide */ public static final int OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION = - AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION; + AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION; /** * Allows an application to capture bugreport directly without consent dialog when using the @@ -1482,33 +1477,31 @@ public class AppOpsManager { * @hide */ public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = - AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD; + AppOpEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD; // App op deprecated/removed. - private static final int OP_DEPRECATED_2 = AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE; + private static final int OP_DEPRECATED_2 = AppOpEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE; /** * Send an intent to launch instead of posting the notification to the status bar. * * @hide */ - public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT; + public static final int OP_USE_FULL_SCREEN_INTENT = AppOpEnums.APP_OP_USE_FULL_SCREEN_INTENT; /** * Hides camera indicator for sandboxed detection apps that directly access the service. * * @hide */ - public static final int OP_CAMERA_SANDBOXED = - AppProtoEnums.APP_OP_CAMERA_SANDBOXED; + public static final int OP_CAMERA_SANDBOXED = AppOpEnums.APP_OP_CAMERA_SANDBOXED; /** * Hides microphone indicator for sandboxed detection apps that directly access the service. * * @hide */ - public static final int OP_RECORD_AUDIO_SANDBOXED = - AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED; + public static final int OP_RECORD_AUDIO_SANDBOXED = AppOpEnums.APP_OP_RECORD_AUDIO_SANDBOXED; /** * Allows the assistant app to be voice-triggered by detected hotwords from a trusted detection @@ -1517,14 +1510,14 @@ public class AppOpsManager { * @hide */ public static final int OP_RECEIVE_SANDBOX_TRIGGER_AUDIO = - AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; + AppOpEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; /** * This op has been deprecated. * */ private static final int OP_DEPRECATED_3 = - AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA; + AppOpEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA; /** * Creation of an overlay using accessibility services @@ -1532,20 +1525,20 @@ public class AppOpsManager { * @hide */ public static final int OP_CREATE_ACCESSIBILITY_OVERLAY = - AppProtoEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY; + AppOpEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY; /** * Indicate that the user has enabled or disabled mobile data * @hide */ public static final int OP_ENABLE_MOBILE_DATA_BY_USER = - AppProtoEnums.APP_OP_ENABLE_MOBILE_DATA_BY_USER; + AppOpEnums.APP_OP_ENABLE_MOBILE_DATA_BY_USER; /** * See {@link #OPSTR_MEDIA_ROUTING_CONTROL}. * @hide */ - public static final int OP_MEDIA_ROUTING_CONTROL = AppProtoEnums.APP_OP_MEDIA_ROUTING_CONTROL; + public static final int OP_MEDIA_ROUTING_CONTROL = AppOpEnums.APP_OP_MEDIA_ROUTING_CONTROL; /** * Op code for use by tests to avoid interfering history logs that the wider system might @@ -1553,7 +1546,7 @@ public class AppOpsManager { * * @hide */ - public static final int OP_RESERVED_FOR_TESTING = AppProtoEnums.APP_OP_RESERVED_FOR_TESTING; + public static final int OP_RESERVED_FOR_TESTING = AppOpEnums.APP_OP_RESERVED_FOR_TESTING; /** * Rapid clearing of notifications by a notification listener @@ -1562,28 +1555,28 @@ public class AppOpsManager { */ // See b/289080543 for more details public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER = - AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER; + AppOpEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER; /** * See {@link #OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER}. * @hide */ public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER = - AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER; + AppOpEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER; /** * This app has been removed.. * * @hide */ - private static final int OP_DEPRECATED_4 = AppProtoEnums.APP_OP_RUN_BACKUP_JOBS; + private static final int OP_DEPRECATED_4 = AppOpEnums.APP_OP_RUN_BACKUP_JOBS; /** * Whether the app has enabled to receive the icon overlay for fetching archived apps. * * @hide */ - public static final int OP_ARCHIVE_ICON_OVERLAY = AppProtoEnums.APP_OP_ARCHIVE_ICON_OVERLAY; + public static final int OP_ARCHIVE_ICON_OVERLAY = AppOpEnums.APP_OP_ARCHIVE_ICON_OVERLAY; /** * Whether the app has enabled compatibility support for unarchival. @@ -1591,7 +1584,7 @@ public class AppOpsManager { * @hide */ public static final int OP_UNARCHIVAL_CONFIRMATION = - AppProtoEnums.APP_OP_UNARCHIVAL_CONFIRMATION; + AppOpEnums.APP_OP_UNARCHIVAL_CONFIRMATION; /** * Allows an app to access location without the traditional location permissions and while the @@ -1603,7 +1596,7 @@ public class AppOpsManager { * * @hide */ - public static final int OP_EMERGENCY_LOCATION = AppProtoEnums.APP_OP_EMERGENCY_LOCATION; + public static final int OP_EMERGENCY_LOCATION = AppOpEnums.APP_OP_EMERGENCY_LOCATION; /** * Allows apps with a NotificationListenerService to receive notifications with sensitive @@ -1613,13 +1606,13 @@ public class AppOpsManager { * @hide */ public static final int OP_RECEIVE_SENSITIVE_NOTIFICATIONS = - AppProtoEnums.APP_OP_RECEIVE_SENSITIVE_NOTIFICATIONS; + AppOpEnums.APP_OP_RECEIVE_SENSITIVE_NOTIFICATIONS; /** @hide Access to read heart rate sensor. */ - public static final int OP_READ_HEART_RATE = AppProtoEnums.APP_OP_READ_HEART_RATE; + public static final int OP_READ_HEART_RATE = AppOpEnums.APP_OP_READ_HEART_RATE; /** @hide Access to read skin temperature. */ - public static final int OP_READ_SKIN_TEMPERATURE = AppProtoEnums.APP_OP_READ_SKIN_TEMPERATURE; + public static final int OP_READ_SKIN_TEMPERATURE = AppOpEnums.APP_OP_READ_SKIN_TEMPERATURE; /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -7807,6 +7800,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; @@ -8861,12 +8964,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(); } @@ -9051,7 +9158,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, @@ -9294,8 +9401,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)); } @@ -9334,13 +9454,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/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 63e039143917..b7285c38290c 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -602,6 +602,15 @@ public class StatusBarManager { @LoggingOnly private static final long MEDIA_CONTROL_BLANK_TITLE = 274775190L; + /** + * Media controls based on {@link android.app.Notification.MediaStyle} notifications will have + * actions from the associated {@link androidx.media3.MediaController}, if available. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT) + // TODO(b/360196209): Set target SDK to Baklava once available + private static final long MEDIA_CONTROL_MEDIA3_ACTIONS = 360196209L; + @UnsupportedAppUsage private Context mContext; private IStatusBarService mService; @@ -1270,6 +1279,21 @@ public class StatusBarManager { } /** + * Checks whether the media controls for a given package should use a Media3 controller + * + * @param packageName App posting media controls + * @param user Current user handle + * @return true if Media3 should be used + * + * @hide + */ + @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG, + android.Manifest.permission.LOG_COMPAT_CHANGE}) + public static boolean useMedia3ControllerForApp(String packageName, UserHandle user) { + return CompatChanges.isChangeEnabled(MEDIA_CONTROL_MEDIA3_ACTIONS, packageName, user); + } + + /** * Checks whether the supplied activity can {@link Activity#startActivityForResult(Intent, int)} * a system activity that captures content on the screen to take a screenshot. * diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 9c6f509c93c1..aac963ade726 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -296,22 +296,22 @@ public class TaskInfo { public boolean isVisibleRequested; /** - * Whether this task is sleeping due to sleeping display. + * Whether the top activity is to be displayed. See {@link android.R.attr#windowNoDisplay}. * @hide */ - public boolean isSleeping; + public boolean isTopActivityNoDisplay; /** - * Whether the top activity fillsParent() is false + * Whether this task is sleeping due to sleeping display. * @hide */ - public boolean isTopActivityTransparent; + public boolean isSleeping; /** - * Whether the top activity has specified style floating. + * Whether the top activity fillsParent() is false * @hide */ - public boolean isTopActivityStyleFloating; + public boolean isTopActivityTransparent; /** * The last non-fullscreen bounds the task was launched in or resized to. @@ -364,8 +364,9 @@ public class TaskInfo { // Do nothing } - private TaskInfo(Parcel source) { - readFromParcel(source); + /** @hide */ + public TaskInfo(Parcel source) { + readTaskFromParcel(source); } /** @@ -482,12 +483,12 @@ public class TaskInfo { && isFocused == that.isFocused && isVisible == that.isVisible && isVisibleRequested == that.isVisibleRequested + && isTopActivityNoDisplay == that.isTopActivityNoDisplay && isSleeping == that.isSleeping && Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId) && parentTaskId == that.parentTaskId && Objects.equals(topActivity, that.topActivity) && isTopActivityTransparent == that.isTopActivityTransparent - && isTopActivityStyleFloating == that.isTopActivityStyleFloating && Objects.equals(lastNonFullscreenBounds, that.lastNonFullscreenBounds) && Objects.equals(capturedLink, that.capturedLink) && capturedLinkTimestamp == that.capturedLinkTimestamp @@ -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(); @@ -561,11 +562,11 @@ public class TaskInfo { isFocused = source.readBoolean(); isVisible = source.readBoolean(); isVisibleRequested = source.readBoolean(); + isTopActivityNoDisplay = source.readBoolean(); isSleeping = source.readBoolean(); mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR); displayAreaFeatureId = source.readInt(); isTopActivityTransparent = source.readBoolean(); - isTopActivityStyleFloating = source.readBoolean(); lastNonFullscreenBounds = source.readTypedObject(Rect.CREATOR); capturedLink = source.readTypedObject(Uri.CREATOR); capturedLinkTimestamp = source.readLong(); @@ -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); @@ -616,11 +618,11 @@ public class TaskInfo { dest.writeBoolean(isFocused); dest.writeBoolean(isVisible); dest.writeBoolean(isVisibleRequested); + dest.writeBoolean(isTopActivityNoDisplay); dest.writeBoolean(isSleeping); dest.writeTypedObject(mTopActivityLocusId, flags); dest.writeInt(displayAreaFeatureId); dest.writeBoolean(isTopActivityTransparent); - dest.writeBoolean(isTopActivityStyleFloating); dest.writeTypedObject(lastNonFullscreenBounds, flags); dest.writeTypedObject(capturedLink, flags); dest.writeLong(capturedLinkTimestamp); @@ -661,11 +663,11 @@ public class TaskInfo { + " isFocused=" + isFocused + " isVisible=" + isVisible + " isVisibleRequested=" + isVisibleRequested + + " isTopActivityNoDisplay=" + isTopActivityNoDisplay + " isSleeping=" + isSleeping + " locusId=" + mTopActivityLocusId + " displayAreaFeatureId=" + displayAreaFeatureId + " isTopActivityTransparent=" + isTopActivityTransparent - + " isTopActivityStyleFloating=" + isTopActivityStyleFloating + " lastNonFullscreenBounds=" + lastNonFullscreenBounds + " capturedLink=" + capturedLink + " capturedLinkTimestamp=" + capturedLinkTimestamp 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/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java new file mode 100644 index 000000000000..df422e0069c5 --- /dev/null +++ b/core/java/android/app/jank/JankTracker.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.jank; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.os.Handler; +import android.os.HandlerThread; +import android.view.AttachedSurfaceControl; +import android.view.Choreographer; +import android.view.SurfaceControl; +import android.view.View; +import android.view.ViewTreeObserver; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; + +/** + * This class is responsible for registering callbacks that will receive JankData batches. + * It handles managing the background thread that JankData will be processed on. As well as acting + * as an intermediary between widgets and the state tracker, routing state changes to the tracker. + * @hide + */ +@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) +public class JankTracker { + + // Tracks states reported by widgets. + private StateTracker mStateTracker; + // Processes JankData batches and associates frames to widget states. + private JankDataProcessor mJankDataProcessor; + + // Background thread responsible for processing JankData batches. + private HandlerThread mHandlerThread = new HandlerThread("AppJankTracker"); + private Handler mHandler = null; + + // Needed so we know when the view is attached to a window. + private ViewTreeObserver mViewTreeObserver; + + // Handle to a registered OnJankData listener. + private SurfaceControl.OnJankDataListenerRegistration mJankDataListenerRegistration; + + // The interface to the windowing system that enables us to register for JankData. + private AttachedSurfaceControl mSurfaceControl; + // Name of the activity that is currently tracking Jank metrics. + private String mActivityName; + // The apps uid. + private int mAppUid; + // View that gives us access to ViewTreeObserver. + private View mDecorView; + + /** + * Set by the activity to enable or disable jank tracking. Activities may disable tracking if + * they are paused or not enable tracking if they are not visible or if the app category is not + * set. + */ + private boolean mTrackingEnabled = false; + /** + * Set to true once listeners are registered and JankData will start to be received. Both + * mTrackingEnabled and mListenersRegistered need to be true for JankData to be processed. + */ + private boolean mListenersRegistered = false; + + + public JankTracker(Choreographer choreographer, View decorView) { + mStateTracker = new StateTracker(choreographer); + mJankDataProcessor = new JankDataProcessor(mStateTracker); + mDecorView = decorView; + mHandlerThread.start(); + registerWindowListeners(); + } + + public void setActivityName(@NonNull String activityName) { + mActivityName = activityName; + } + + public void setAppUid(int uid) { + mAppUid = uid; + } + + /** + * Will add the widget category, id and state as a UI state to associate frames to it. + * @param widgetCategory preselected general widget category + * @param widgetId developer defined widget id if available. + * @param widgetState the current active widget state. + */ + public void addUiState(String widgetCategory, String widgetId, String widgetState) { + if (!shouldTrack()) return; + + mStateTracker.putState(widgetCategory, widgetId, widgetState); + } + + /** + * Will remove the widget category, id and state as a ui state and no longer attribute frames + * to it. + * @param widgetCategory preselected general widget category + * @param widgetId developer defined widget id if available. + * @param widgetState no longer active widget state. + */ + public void removeUiState(String widgetCategory, String widgetId, String widgetState) { + if (!shouldTrack()) return; + + mStateTracker.removeState(widgetCategory, widgetId, widgetState); + } + + /** + * Call to update a jank state to a different state. + * @param widgetCategory preselected general widget category. + * @param widgetId developer defined widget id if available. + * @param currentState current state of the widget. + * @param nextState the state the widget will be in. + */ + public void updateUiState(String widgetCategory, String widgetId, String currentState, + String nextState) { + if (!shouldTrack()) return; + + mStateTracker.updateState(widgetCategory, widgetId, currentState, nextState); + } + + /** + * Will enable jank tracking, and add the activity as a state to associate frames to. + */ + public void enableAppJankTracking() { + // Add the activity as a state, this will ensure we track frames to the activity without the + // need of a decorated widget to be used. + // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged. + mStateTracker.putState("NONE", mActivityName, "NONE"); + mTrackingEnabled = true; + } + + /** + * Will disable jank tracking, and remove the activity as a state to associate frames to. + */ + public void disableAppJankTracking() { + mTrackingEnabled = false; + // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged. + mStateTracker.removeState("NONE", mActivityName, "NONE"); + } + + /** + * Retrieve all pending widget states, this is intended for testing purposes only. + * @param stateDataList the ArrayList that will be populated with the pending states. + */ + @VisibleForTesting + public void getAllUiStates(@NonNull ArrayList<StateTracker.StateData> stateDataList) { + mStateTracker.retrieveAllStates(stateDataList); + } + + /** + * Only intended to be used by tests, the runnable that registers the listeners may not run + * in time for tests to pass. This forces them to run immediately. + */ + @VisibleForTesting + public void forceListenerRegistration() { + mSurfaceControl = mDecorView.getRootSurfaceControl(); + registerForJankData(); + // TODO b/376116199 Check if registration is good. + mListenersRegistered = true; + } + + private void registerForJankData() { + if (mSurfaceControl == null) return; + /* + TODO b/376115668 Register for JankData batches from new JankTracking API + */ + } + + private boolean shouldTrack() { + return mTrackingEnabled && mListenersRegistered; + } + + /** + * Need to know when the decor view gets attached to the window in order to get + * AttachedSurfaceControl. In order to register a callback for OnJankDataListener + * AttachedSurfaceControl needs to be created which only happens after onWindowAttached is + * called. This is why there is a delay in posting the runnable. + */ + private void registerWindowListeners() { + if (mDecorView == null) return; + mViewTreeObserver = mDecorView.getViewTreeObserver(); + mViewTreeObserver.addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() { + @Override + public void onWindowAttached() { + getHandler().postDelayed(new Runnable() { + @Override + public void run() { + forceListenerRegistration(); + } + }, 1000); + } + + @Override + public void onWindowDetached() { + // TODO b/376116199 do we un-register the callback or just not process the data. + } + }); + } + + private Handler getHandler() { + if (mHandler == null) { + mHandler = new Handler(mHandlerThread.getLooper()); + } + return mHandler; + } +} diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig index 940a4d208d99..ce515761551c 100644 --- a/core/java/android/appwidget/flags.aconfig +++ b/core/java/android/appwidget/flags.aconfig @@ -55,7 +55,7 @@ flag { name: "remote_views_proto" namespace: "app_widgets" description: "Enable support for persisting RemoteViews previews to Protobuf" - bug: "306546610" + bug: "364420494" } flag { diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index 65f9cbefb052..2be27dabcf90 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -160,7 +160,7 @@ public final class VirtualDeviceParams implements Parcelable { */ @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO, POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CLIPBOARD, POLICY_TYPE_CAMERA, - POLICY_TYPE_BLOCKED_ACTIVITY}) + POLICY_TYPE_BLOCKED_ACTIVITY, POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS}) @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) public @interface PolicyType {} @@ -301,6 +301,21 @@ public final class VirtualDeviceParams implements Parcelable { @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API) public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6; + /** + * Tells the virtual device framework how to handle camera access of the default device by apps + * running on the virtual device. + * + * <ul> + * <li>{@link #DEVICE_POLICY_DEFAULT}: Default device camera access will be allowed. + * <li>{@link #DEVICE_POLICY_CUSTOM}: Default device camera access will be blocked. + * </ul> + * + * @see Context#DEVICE_ID_DEFAULT + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags + .FLAG_DEFAULT_DEVICE_CAMERA_ACCESS_POLICY) + public static final int POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS = 7; + private final int mLockState; @NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts; @NavigationPolicy @@ -318,6 +333,8 @@ public final class VirtualDeviceParams implements Parcelable { @Nullable private final IVirtualSensorCallback mVirtualSensorCallback; private final int mAudioPlaybackSessionId; private final int mAudioRecordingSessionId; + private final long mDimDuration; + private final long mScreenOffTimeout; private VirtualDeviceParams( @LockState int lockState, @@ -333,7 +350,9 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull List<VirtualSensorConfig> virtualSensorConfigs, @Nullable IVirtualSensorCallback virtualSensorCallback, int audioPlaybackSessionId, - int audioRecordingSessionId) { + int audioRecordingSessionId, + long dimDuration, + long screenOffTimeout) { mLockState = lockState; mUsersWithMatchingAccounts = new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts)); @@ -351,6 +370,8 @@ public final class VirtualDeviceParams implements Parcelable { mVirtualSensorCallback = virtualSensorCallback; mAudioPlaybackSessionId = audioPlaybackSessionId; mAudioRecordingSessionId = audioRecordingSessionId; + mDimDuration = dimDuration; + mScreenOffTimeout = screenOffTimeout; } @SuppressWarnings("unchecked") @@ -371,6 +392,8 @@ public final class VirtualDeviceParams implements Parcelable { mAudioRecordingSessionId = parcel.readInt(); mHomeComponent = parcel.readTypedObject(ComponentName.CREATOR); mInputMethodComponent = parcel.readTypedObject(ComponentName.CREATOR); + mDimDuration = parcel.readLong(); + mScreenOffTimeout = parcel.readLong(); } /** @@ -382,6 +405,26 @@ public final class VirtualDeviceParams implements Parcelable { } /** + * Returns the dim duration for the displays of this device. + * + * @see Builder#setDimDuration(Duration) + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + public @NonNull Duration getDimDuration() { + return Duration.ofMillis(mDimDuration); + } + + /** + * Returns the screen off timeout of the displays of this device. + * + * @see Builder#setDimDuration(Duration) + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + public @NonNull Duration getScreenOffTimeout() { + return Duration.ofMillis(mScreenOffTimeout); + } + + /** * Returns the custom component used as home on all displays owned by this virtual device that * support home activities. * @@ -604,6 +647,8 @@ public final class VirtualDeviceParams implements Parcelable { dest.writeInt(mAudioRecordingSessionId); dest.writeTypedObject(mHomeComponent, flags); dest.writeTypedObject(mInputMethodComponent, flags); + dest.writeLong(mDimDuration); + dest.writeLong(mScreenOffTimeout); } @Override @@ -638,7 +683,9 @@ public final class VirtualDeviceParams implements Parcelable { && Objects.equals(mHomeComponent, that.mHomeComponent) && Objects.equals(mInputMethodComponent, that.mInputMethodComponent) && mAudioPlaybackSessionId == that.mAudioPlaybackSessionId - && mAudioRecordingSessionId == that.mAudioRecordingSessionId; + && mAudioRecordingSessionId == that.mAudioRecordingSessionId + && mDimDuration == that.mDimDuration + && mScreenOffTimeout == that.mScreenOffTimeout; } @Override @@ -647,7 +694,7 @@ public final class VirtualDeviceParams implements Parcelable { mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExemptions, mDefaultNavigationPolicy, mActivityPolicyExemptions, mDefaultActivityPolicy, mName, mDevicePolicies, mHomeComponent, mInputMethodComponent, mAudioPlaybackSessionId, - mAudioRecordingSessionId); + mAudioRecordingSessionId, mDimDuration, mScreenOffTimeout); for (int i = 0; i < mDevicePolicies.size(); i++) { hashCode = 31 * hashCode + mDevicePolicies.keyAt(i); hashCode = 31 * hashCode + mDevicePolicies.valueAt(i); @@ -671,6 +718,8 @@ public final class VirtualDeviceParams implements Parcelable { + " mInputMethodComponent=" + mInputMethodComponent + " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId + " mAudioRecordingSessionId=" + mAudioRecordingSessionId + + " mDimDuration=" + mDimDuration + + " mScreenOffTimeout=" + mScreenOffTimeout + ")"; } @@ -692,11 +741,13 @@ public final class VirtualDeviceParams implements Parcelable { pw.println(prefix + "mInputMethodComponent=" + mInputMethodComponent); pw.println(prefix + "mAudioPlaybackSessionId=" + mAudioPlaybackSessionId); pw.println(prefix + "mAudioRecordingSessionId=" + mAudioRecordingSessionId); + pw.println(prefix + "mDimDuration=" + mDimDuration); + pw.println(prefix + "mScreenOffTimeout=" + mScreenOffTimeout); } @NonNull public static final Parcelable.Creator<VirtualDeviceParams> CREATOR = - new Parcelable.Creator<VirtualDeviceParams>() { + new Parcelable.Creator<>() { public VirtualDeviceParams createFromParcel(Parcel in) { return new VirtualDeviceParams(in); } @@ -711,6 +762,8 @@ public final class VirtualDeviceParams implements Parcelable { */ public static final class Builder { + private static final Duration INFINITE_TIMEOUT = Duration.ofDays(365 * 1000); + private @LockState int mLockState = LOCK_STATE_DEFAULT; @NonNull private Set<UserHandle> mUsersWithMatchingAccounts = Collections.emptySet(); @NonNull private Set<ComponentName> mCrossTaskNavigationExemptions = Collections.emptySet(); @@ -733,6 +786,8 @@ public final class VirtualDeviceParams implements Parcelable { @Nullable private VirtualSensorDirectChannelCallback mVirtualSensorDirectChannelCallback; @Nullable private ComponentName mHomeComponent; @Nullable private ComponentName mInputMethodComponent; + private Duration mDimDuration = Duration.ZERO; + private Duration mScreenOffTimeout = Duration.ZERO; private static class VirtualSensorCallbackDelegate extends IVirtualSensorCallback.Stub { @NonNull @@ -810,6 +865,57 @@ public final class VirtualDeviceParams implements Parcelable { } /** + * Sets the dim duration for all trusted non-mirror displays of the device. + * + * <p>The system will reduce the display brightness for the specified duration if there + * has been no interaction just before the displays turn off.</p> + * + * <p>If set, the screen off timeout must also be set to a value larger than the dim + * duration. If left unset or set to zero, then the display brightness will not be reduced. + * </p> + * + * @throws IllegalArgumentException if the dim duration is negative or if the dim duration + * is longer than the screen off timeout. + * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED + * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR + * @see #setScreenOffTimeout + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @NonNull + public Builder setDimDuration(@NonNull Duration dimDuration) { + if (Objects.requireNonNull(dimDuration).compareTo(Duration.ZERO) < 0) { + throw new IllegalArgumentException("The dim duration cannot be negative"); + } + mDimDuration = dimDuration; + return this; + } + + /** + * Sets the timeout, after which all trusted non-mirror displays of the device will turn + * off, if there has been no interaction with the device. + * + * <p>If dim duration is set, the screen off timeout must be set to a value larger than the + * dim duration. If left unset or set to zero, then the displays will never be turned off + * due to inactivity.</p> + * + * @throws IllegalArgumentException if the screen off timeout is negative or if the dim + * duration is longer than the screen off timeout. + * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED + * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR + * @see #setDimDuration + * @see VirtualDeviceManager.VirtualDevice#goToSleep() + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @NonNull + public Builder setScreenOffTimeout(@NonNull Duration screenOffTimeout) { + if (Objects.requireNonNull(screenOffTimeout).compareTo(Duration.ZERO) < 0) { + throw new IllegalArgumentException("The screen off timeout cannot be negative"); + } + mScreenOffTimeout = screenOffTimeout; + return this; + } + + /** * Specifies a component to be used as home on all displays owned by this virtual device * that support home activities. * * @@ -1205,6 +1311,14 @@ public final class VirtualDeviceParams implements Parcelable { } } + if (mDimDuration.compareTo(mScreenOffTimeout) > 0) { + throw new IllegalArgumentException( + "The dim duration cannot be greater than the screen off timeout."); + } + if (mScreenOffTimeout.compareTo(Duration.ZERO) == 0) { + mScreenOffTimeout = INFINITE_TIMEOUT; + } + if (!Flags.crossDeviceClipboard()) { mDevicePolicies.delete(POLICY_TYPE_CLIPBOARD); } @@ -1213,6 +1327,10 @@ public final class VirtualDeviceParams implements Parcelable { mDevicePolicies.delete(POLICY_TYPE_CAMERA); } + if (!android.companion.virtualdevice.flags.Flags.defaultDeviceCameraAccessPolicy()) { + mDevicePolicies.delete(POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS); + } + if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) { mDevicePolicies.delete(POLICY_TYPE_BLOCKED_ACTIVITY); } @@ -1250,7 +1368,9 @@ public final class VirtualDeviceParams implements Parcelable { mVirtualSensorConfigs, virtualSensorCallbackDelegate, mAudioPlaybackSessionId, - mAudioRecordingSessionId); + mAudioRecordingSessionId, + mDimDuration.toMillis(), + mScreenOffTimeout.toMillis()); } } } diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index fc9c94dd5b0f..3e6919bac5fa 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -67,13 +67,6 @@ flag { } flag { - name: "stream_camera" - namespace: "virtual_devices" - description: "Enable streaming camera to Virtual Devices" - bug: "291740640" -} - -flag { name: "persistent_device_id_api" is_exported: true namespace: "virtual_devices" diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING index c2febaeec73a..e8893e47ddc8 100644 --- a/core/java/android/content/res/TEST_MAPPING +++ b/core/java/android/content/res/TEST_MAPPING @@ -5,6 +5,9 @@ }, { "path": "frameworks/base/core/tests/coretests/src/com/android/internal/content/res" + }, + { + "path": "platform_testing/libraries/screenshot" } ], "presubmit": [ 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/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java index 41585b3571d6..7d6e7ad857d1 100644 --- a/core/java/android/database/BulkCursorNative.java +++ b/core/java/android/database/BulkCursorNative.java @@ -215,7 +215,7 @@ final class BulkCursorProxy implements IBulkCursor { // If close() is being called from the finalizer thread, do not wait for a reply from // the remote side. final boolean fromFinalizer = - android.database.sqlite.Flags.onewayFinalizerClose() + android.database.sqlite.Flags.onewayFinalizerCloseFixed() && "FinalizerDaemon".equals(Thread.currentThread().getName()); mRemote.transact(CLOSE_TRANSACTION, data, reply, fromFinalizer ? IBinder.FLAG_ONEWAY: 0); diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig index 826b908e6775..d43a66904af8 100644 --- a/core/java/android/database/sqlite/flags.aconfig +++ b/core/java/android/database/sqlite/flags.aconfig @@ -2,8 +2,9 @@ package: "android.database.sqlite" container: "system" flag { - name: "oneway_finalizer_close" + name: "oneway_finalizer_close_fixed" namespace: "system_performance" + is_fixed_read_only: true description: "Make BuildCursorNative.close oneway if in the the finalizer" bug: "368221351" } 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/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index bce95187515a..39dddb723cb9 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -279,4 +279,6 @@ interface IInputManager { void removeAllCustomInputGestures(); AidlInputGestureData[] getCustomInputGestures(); + + AidlInputGestureData[] getAppLaunchBookmarks(); } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 876ba1021917..2051dbe7fb2e 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1494,9 +1494,8 @@ public final class InputManager { try { return mIm.addCustomInputGesture(inputGestureData.getAidlData()); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } - return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER; } /** Removes an existing custom gesture @@ -1517,9 +1516,8 @@ public final class InputManager { try { return mIm.removeCustomInputGesture(inputGestureData.getAidlData()); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } - return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER; } /** Removes all custom input gestures @@ -1534,7 +1532,7 @@ public final class InputManager { try { mIm.removeAllCustomInputGestures(); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -1552,12 +1550,32 @@ public final class InputManager { result.add(new InputGestureData(data)); } } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } return result; } /** + * Return the set of application launch bookmarks handled by the input framework. + * + * @return list of {@link InputGestureData} containing the application launch shortcuts parsed + * at boot time from {@code bookmarks.xml}. + * + * @hide + */ + public List<InputGestureData> getAppLaunchBookmarks() { + try { + List<InputGestureData> result = new ArrayList<>(); + for (AidlInputGestureData data : mIm.getAppLaunchBookmarks()) { + result.add(new InputGestureData(data)); + } + return result; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * A callback used to be notified about battery state changes for an input device. The * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the * listener is successfully registered to provide the initial battery state of the device. 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/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/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS index a753f9634d0d..37604bc2eb65 100644 --- a/core/java/android/hardware/usb/OWNERS +++ b/core/java/android/hardware/usb/OWNERS @@ -1,7 +1,7 @@ # Bug component: 175220 -aprasath@google.com -kumarashishg@google.com -sarup@google.com anothermark@google.com +febinthattil@google.com +aprasath@google.com badhri@google.com +kumarashishg@google.com
\ No newline at end of file diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 8c3f0ef08039..ae8366817f2b 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -55,6 +55,7 @@ import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECT import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER; import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED; import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING; +import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API; import static android.view.inputmethod.Flags.ctrlShiftShortcut; import static android.view.inputmethod.Flags.predictiveBackIme; @@ -4392,6 +4393,39 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * Called when the requested visibility of a custom IME Switcher button changes. + * + * <p>When the system provides an IME navigation bar, it may decide to show an IME Switcher + * button inside this bar. However, the IME can request hiding the bar provided by the system + * with {@code getWindowInsetsController().hide(captionBar())} (the IME navigation bar provides + * {@link Type#captionBar() captionBar} insets to the IME window). If the request is successful, + * then it becomes the IME's responsibility to provide a custom IME Switcher button in its + * input view, with equivalent functionality.</p> + * + * <p>This custom button is only requested to be visible when the system provides the IME + * navigation bar, both the bar and the IME Switcher button inside it should be visible, + * but the IME successfully requested to hide the bar. This does not depend on the current + * visibility of the IME. It could be called with {@code true} while the IME is hidden, in + * which case the IME should prepare to show the button as soon as the IME itself is shown.</p> + * + * <p>This is only called when the requested visibility changes. The default value is + * {@code false} and as such, this will not be called initially if the resulting value is + * {@code false}.</p> + * + * <p>This can be called at any time after {@link #onCreate}, even if the IME is not currently + * visible. However, this is not guaranteed to be called before the IME is shown, as it depends + * on when the IME requested hiding the IME navigation bar. If the request is sent during + * the showing flow (e.g. during {@link #onStartInputView}), this will be called shortly after + * {@link #onWindowShown}, but before the first IME frame is drawn.</p> + * + * @param visible whether the button is requested visible or not. + */ + @FlaggedApi(FLAG_IME_SWITCHER_REVAMP_API) + public void onCustomImeSwitcherButtonRequestedVisible(boolean visible) { + // Intentionally empty + } + + /** * Called when the IME switch button was clicked from the client. Depending on the number of * enabled IME subtypes, this will either switch to the next IME/subtype, or show the input * method picker dialog. diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index b08454dd7f8f..38be8d9f772d 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -41,6 +41,7 @@ import android.view.WindowInsets; import android.view.WindowInsetsController.Appearance; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import android.view.inputmethod.Flags; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; @@ -178,6 +179,9 @@ final class NavigationBarController { private boolean mDrawLegacyNavigationBarBackground; + /** Whether a custom IME Switcher button should be visible. */ + private boolean mCustomImeSwitcherVisible; + private final Rect mTempRect = new Rect(); private final int[] mTempPos = new int[2]; @@ -265,6 +269,7 @@ final class NavigationBarController { // IME navigation bar. boolean visible = insets.isVisible(captionBar()); mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE); + checkCustomImeSwitcherVisibility(); } return view.onApplyWindowInsets(insets); }); @@ -491,6 +496,8 @@ final class NavigationBarController { mShouldShowImeSwitcherWhenImeIsShown; mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown; + checkCustomImeSwitcherVisibility(); + mService.mWindow.getWindow().getDecorView().getWindowInsetsController() .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight(imeDrawsImeNavBar)); @@ -616,12 +623,33 @@ final class NavigationBarController { && mNavigationBarFrame.getVisibility() == View.VISIBLE; } + /** + * Checks if a custom IME Switcher button should be visible, and notifies the IME when this + * state changes. This can only be {@code true} if three conditions are met: + * + * <li>The IME should draw the IME navigation bar.</li> + * <li>The IME Switcher button should be visible when the IME is visible.</li> + * <li>The IME navigation bar should be visible, but was requested hidden by the IME.</li> + */ + private void checkCustomImeSwitcherVisibility() { + if (!Flags.imeSwitcherRevampApi()) { + return; + } + final boolean visible = mImeDrawsImeNavBar && mShouldShowImeSwitcherWhenImeIsShown + && mNavigationBarFrame != null && !isShown(); + if (visible != mCustomImeSwitcherVisible) { + mCustomImeSwitcherVisible = visible; + mService.onCustomImeSwitcherButtonRequestedVisible(mCustomImeSwitcherVisible); + } + } + @Override public String toDebugString() { return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar + " mNavigationBarFrame=" + mNavigationBarFrame + " mShouldShowImeSwitcherWhenImeIsShown=" + mShouldShowImeSwitcherWhenImeIsShown + + " mCustomImeSwitcherVisible=" + mCustomImeSwitcherVisible + " mAppearance=0x" + Integer.toHexString(mAppearance) + " mDarkIntensity=" + mDarkIntensity + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index 6c3c2852c7e7..5ae425f184c7 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -1315,7 +1315,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { } synchronized (BatteryUsageStats.class) { - if (!sInstances.isEmpty()) { + if (sInstances != null && !sInstances.isEmpty()) { Exception callSite = sInstances.entrySet().iterator().next().getValue(); int count = sInstances.size(); sInstances.clear(); diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 13d7e3c2fbfd..b3aebad1b160 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -40,8 +40,6 @@ import android.util.ArraySet; import android.util.Slog; import android.view.View; -import com.android.internal.ravenwood.RavenwoodEnvironment; - import dalvik.system.VMRuntime; import java.lang.annotation.Retention; @@ -57,10 +55,6 @@ import java.util.stream.Collectors; */ @RavenwoodKeepWholeClass public class Build { - static { - // Set up the default system properties. - RavenwoodEnvironment.ensureRavenwoodInitialized(); - } private static final String TAG = "Build"; /** Value used for when a build property is unknown. */ diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index e80efd2a9380..60eeb2b8b0d5 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -41,7 +41,6 @@ import android.content.ContentResolver; import android.net.Uri; import android.os.MessageQueue.OnFileDescriptorEventListener; import android.ravenwood.annotation.RavenwoodKeepWholeClass; -import android.ravenwood.annotation.RavenwoodReplace; import android.ravenwood.annotation.RavenwoodThrow; import android.system.ErrnoException; import android.system.Os; @@ -51,8 +50,6 @@ import android.util.CloseGuard; import android.util.Log; import android.util.Slog; -import com.android.internal.ravenwood.RavenwoodEnvironment; - import dalvik.system.VMRuntime; import libcore.io.IoUtils; @@ -1254,15 +1251,10 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } } - @RavenwoodReplace private static boolean isAtLeastQ() { return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q); } - private static boolean isAtLeastQ$ravenwood() { - return RavenwoodEnvironment.workaround().isTargetSdkAtLeastQ(); - } - private static int ifAtLeastQ(int value) { return isAtLeastQ() ? value : 0; } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 71d29af6cf02..e7282435ad46 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -29,6 +29,11 @@ import android.annotation.TestApi; import android.annotation.UptimeMillisLong; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build.VERSION_CODES; +import android.ravenwood.annotation.RavenwoodKeep; +import android.ravenwood.annotation.RavenwoodKeepPartialClass; +import android.ravenwood.annotation.RavenwoodRedirect; +import android.ravenwood.annotation.RavenwoodRedirectionClass; +import android.ravenwood.annotation.RavenwoodReplace; import android.sysprop.MemoryProperties; import android.system.ErrnoException; import android.system.Os; @@ -37,8 +42,6 @@ import android.system.StructPollfd; import android.util.Pair; import android.webkit.WebViewZygote; -import com.android.internal.os.SomeArgs; -import com.android.internal.util.Preconditions; import com.android.sdksandbox.flags.Flags; import dalvik.system.VMDebug; @@ -55,6 +58,8 @@ import java.util.concurrent.TimeoutException; /** * Tools for managing OS processes. */ +@RavenwoodKeepPartialClass +@RavenwoodRedirectionClass("Process_ravenwood") public class Process { private static final String LOG_TAG = "Process"; @@ -671,7 +676,6 @@ public class Process { */ public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess(); - /** * The process name set via {@link #setArgV0(String)}. */ @@ -845,47 +849,20 @@ public class Process { /** * Returns true if the current process is a 64-bit runtime. */ - @android.ravenwood.annotation.RavenwoodKeep + @RavenwoodKeep public static final boolean is64Bit() { return VMRuntime.getRuntime().is64Bit(); } - private static volatile ThreadLocal<SomeArgs> sIdentity$ravenwood; - - /** @hide */ - @android.ravenwood.annotation.RavenwoodKeep - public static void init$ravenwood(final int uid, final int pid) { - sIdentity$ravenwood = ThreadLocal.withInitial(() -> { - final SomeArgs args = SomeArgs.obtain(); - args.argi1 = uid; - args.argi2 = pid; - args.argi3 = Long.hashCode(Thread.currentThread().getId()); - args.argi4 = THREAD_PRIORITY_DEFAULT; - args.arg1 = Boolean.TRUE; // backgroundOk - return args; - }); - } - - /** @hide */ - @android.ravenwood.annotation.RavenwoodKeep - public static void reset$ravenwood() { - sIdentity$ravenwood = null; - } - /** * Returns the identifier of this process, which can be used with * {@link #killProcess} and {@link #sendSignal}. */ - @android.ravenwood.annotation.RavenwoodReplace + @RavenwoodKeep public static final int myPid() { return Os.getpid(); } - /** @hide */ - public static final int myPid$ravenwood() { - return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi2; - } - /** * Returns the identifier of this process' parent. * @hide @@ -899,39 +876,29 @@ public class Process { * Returns the identifier of the calling thread, which be used with * {@link #setThreadPriority(int, int)}. */ - @android.ravenwood.annotation.RavenwoodReplace + @RavenwoodKeep public static final int myTid() { return Os.gettid(); } - /** @hide */ - public static final int myTid$ravenwood() { - return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi3; - } - /** * Returns the identifier of this process's uid. This is the kernel uid * that the process is running under, which is the identity of its * app-specific sandbox. It is different from {@link #myUserHandle} in that * a uid identifies a specific app sandbox in a specific user. */ - @android.ravenwood.annotation.RavenwoodReplace + @RavenwoodKeep public static final int myUid() { return Os.getuid(); } - /** @hide */ - public static final int myUid$ravenwood() { - return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi1; - } - /** * Returns this process's user handle. This is the * user the process is running under. It is distinct from * {@link #myUid()} in that a particular user will have multiple * distinct apps running under it each with their own uid. */ - @android.ravenwood.annotation.RavenwoodKeep + @RavenwoodKeep public static UserHandle myUserHandle() { return UserHandle.of(UserHandle.getUserId(myUid())); } @@ -940,7 +907,7 @@ public class Process { * Returns whether the given uid belongs to a system core component or not. * @hide */ - @android.ravenwood.annotation.RavenwoodKeep + @RavenwoodKeep public static boolean isCoreUid(int uid) { return UserHandle.isCore(uid); } @@ -951,7 +918,7 @@ public class Process { * @return Whether the uid corresponds to an application sandbox running in * a specific user. */ - @android.ravenwood.annotation.RavenwoodKeep + @RavenwoodKeep public static boolean isApplicationUid(int uid) { return UserHandle.isApp(uid); } @@ -959,7 +926,7 @@ public class Process { /** * Returns whether the current process is in an isolated sandbox. */ - @android.ravenwood.annotation.RavenwoodKeep + @RavenwoodKeep public static final boolean isIsolated() { return isIsolated(myUid()); } @@ -971,7 +938,7 @@ public class Process { @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.") - @android.ravenwood.annotation.RavenwoodKeep + @RavenwoodKeep public static final boolean isIsolated(int uid) { return isIsolatedUid(uid); } @@ -979,7 +946,7 @@ public class Process { /** * Returns whether the process with the given {@code uid} is an isolated sandbox. */ - @android.ravenwood.annotation.RavenwoodKeep + @RavenwoodKeep public static final boolean isIsolatedUid(int uid) { uid = UserHandle.getAppId(uid); return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID) @@ -991,7 +958,7 @@ public class Process { * @see android.app.sdksandbox.SdkSandboxManager */ @SuppressLint("UnflaggedApi") // promoting from @SystemApi. - @android.ravenwood.annotation.RavenwoodKeep + @RavenwoodKeep public static final boolean isSdkSandboxUid(int uid) { uid = UserHandle.getAppId(uid); return (uid >= FIRST_SDK_SANDBOX_UID && uid <= LAST_SDK_SANDBOX_UID); @@ -1007,7 +974,7 @@ public class Process { * @throws IllegalArgumentException if input is not an sdk sandbox uid */ @SuppressLint("UnflaggedApi") // promoting from @SystemApi. - @android.ravenwood.annotation.RavenwoodKeep + @RavenwoodKeep public static final int getAppUidForSdkSandboxUid(int uid) { if (!isSdkSandboxUid(uid)) { throw new IllegalArgumentException("Input UID is not an SDK sandbox UID"); @@ -1023,7 +990,7 @@ public class Process { */ @SystemApi(client = MODULE_LIBRARIES) @TestApi - @android.ravenwood.annotation.RavenwoodKeep + @RavenwoodKeep // TODO(b/318651609): Deprecate once Process#getSdkSandboxUidForAppUid is rolled out to 100% public static final int toSdkSandboxUid(int uid) { return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID); @@ -1039,7 +1006,7 @@ public class Process { * @throws IllegalArgumentException if input is not an app uid */ @FlaggedApi(Flags.FLAG_SDK_SANDBOX_UID_TO_APP_UID_API) - @android.ravenwood.annotation.RavenwoodKeep + @RavenwoodKeep public static final int getSdkSandboxUidForAppUid(int uid) { if (!isApplicationUid(uid)) { throw new IllegalArgumentException("Input UID is not an app UID"); @@ -1050,7 +1017,7 @@ public class Process { /** * Returns whether the current process is a sdk sandbox process. */ - @android.ravenwood.annotation.RavenwoodKeep + @RavenwoodKeep public static final boolean isSdkSandbox() { return isSdkSandboxUid(myUid()); } @@ -1127,28 +1094,11 @@ public class Process { * not have permission to modify the given thread, or to use the given * priority. */ - @android.ravenwood.annotation.RavenwoodReplace + @RavenwoodRedirect public static final native void setThreadPriority(int tid, @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority) throws IllegalArgumentException, SecurityException; - /** @hide */ - public static final void setThreadPriority$ravenwood(int tid, int priority) { - final SomeArgs args = - Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get(); - if (args.argi3 == tid) { - boolean backgroundOk = (args.arg1 == Boolean.TRUE); - if (priority >= THREAD_PRIORITY_BACKGROUND && !backgroundOk) { - throw new IllegalArgumentException( - "Priority " + priority + " blocked by setCanSelfBackground()"); - } - args.argi4 = priority; - } else { - throw new UnsupportedOperationException( - "Cross-thread priority management not yet available in Ravenwood"); - } - } - /** * Call with 'false' to cause future calls to {@link #setThreadPriority(int)} to * throw an exception if passed a background-level thread priority. This is only @@ -1156,16 +1106,9 @@ public class Process { * * @hide */ - @android.ravenwood.annotation.RavenwoodReplace + @RavenwoodRedirect public static final native void setCanSelfBackground(boolean backgroundOk); - /** @hide */ - public static final void setCanSelfBackground$ravenwood(boolean backgroundOk) { - final SomeArgs args = - Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get(); - args.arg1 = Boolean.valueOf(backgroundOk); - } - /** * Sets the scheduling group for a thread. * @hide @@ -1294,13 +1237,12 @@ public class Process { * * @see #setThreadPriority(int, int) */ - @android.ravenwood.annotation.RavenwoodReplace + @RavenwoodReplace public static final native void setThreadPriority( @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority) throws IllegalArgumentException, SecurityException; - /** @hide */ - public static final void setThreadPriority$ravenwood(int priority) { + private static void setThreadPriority$ravenwood(int priority) { setThreadPriority(myTid(), priority); } @@ -1317,23 +1259,11 @@ public class Process { * @throws IllegalArgumentException Throws IllegalArgumentException if * <var>tid</var> does not exist. */ - @android.ravenwood.annotation.RavenwoodReplace + @RavenwoodRedirect @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) public static final native int getThreadPriority(int tid) throws IllegalArgumentException; - /** @hide */ - public static final int getThreadPriority$ravenwood(int tid) { - final SomeArgs args = - Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get(); - if (args.argi3 == tid) { - return args.argi4; - } else { - throw new UnsupportedOperationException( - "Cross-thread priority management not yet available in Ravenwood"); - } - } - /** * Return the current scheduling policy of a thread, based on Linux. * diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 90993e1850d4..edeb75b6193d 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -365,7 +365,7 @@ public final class StrictMode { public static final int NETWORK_POLICY_REJECT = 2; /** - * Detect explicit calls to {@link Runtime#gc()}. + * Detects explicit calls to {@link Runtime#gc()}. */ @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @@ -501,7 +501,7 @@ public final class StrictMode { private Executor mExecutor; /** - * Create a Builder that detects nothing and has no violations. (but note that {@link + * Creates a Builder that detects nothing and has no violations. (but note that {@link * #build} will default to enabling {@link #penaltyLog} if no other penalties are * specified) */ @@ -509,7 +509,7 @@ public final class StrictMode { mMask = 0; } - /** Initialize a Builder from an existing ThreadPolicy. */ + /** Initializes a Builder from an existing ThreadPolicy. */ public Builder(ThreadPolicy policy) { mMask = policy.mask; mListener = policy.mListener; @@ -517,7 +517,7 @@ public final class StrictMode { } /** - * Detect everything that's potentially suspect. + * Detects everything that's potentially suspect. * * <p>As of the Gingerbread release this includes network and disk operations but will * likely expand in future releases. @@ -544,52 +544,52 @@ public final class StrictMode { return this; } - /** Disable the detection of everything. */ + /** Disables the detection of everything. */ public @NonNull Builder permitAll() { return disable(DETECT_THREAD_ALL); } - /** Enable detection of network operations. */ + /** Enables detection of network operations. */ public @NonNull Builder detectNetwork() { return enable(DETECT_THREAD_NETWORK); } - /** Disable detection of network operations. */ + /** Disables detection of network operations. */ public @NonNull Builder permitNetwork() { return disable(DETECT_THREAD_NETWORK); } - /** Enable detection of disk reads. */ + /** Enables detection of disk reads. */ public @NonNull Builder detectDiskReads() { return enable(DETECT_THREAD_DISK_READ); } - /** Disable detection of disk reads. */ + /** Disables detection of disk reads. */ public @NonNull Builder permitDiskReads() { return disable(DETECT_THREAD_DISK_READ); } - /** Enable detection of slow calls. */ + /** Enables detection of slow calls. */ public @NonNull Builder detectCustomSlowCalls() { return enable(DETECT_THREAD_CUSTOM); } - /** Disable detection of slow calls. */ + /** Disables detection of slow calls. */ public @NonNull Builder permitCustomSlowCalls() { return disable(DETECT_THREAD_CUSTOM); } - /** Disable detection of mismatches between defined resource types and getter calls. */ + /** Disables detection of mismatches between defined resource types and getter calls. */ public @NonNull Builder permitResourceMismatches() { return disable(DETECT_THREAD_RESOURCE_MISMATCH); } - /** Detect unbuffered input/output operations. */ + /** Detects unbuffered input/output operations. */ public @NonNull Builder detectUnbufferedIo() { return enable(DETECT_THREAD_UNBUFFERED_IO); } - /** Disable detection of unbuffered input/output operations. */ + /** Disables detection of unbuffered input/output operations. */ public @NonNull Builder permitUnbufferedIo() { return disable(DETECT_THREAD_UNBUFFERED_IO); } @@ -610,32 +610,32 @@ public final class StrictMode { return enable(DETECT_THREAD_RESOURCE_MISMATCH); } - /** Enable detection of disk writes. */ + /** Enables detection of disk writes. */ public @NonNull Builder detectDiskWrites() { return enable(DETECT_THREAD_DISK_WRITE); } - /** Disable detection of disk writes. */ + /** Disables detection of disk writes. */ public @NonNull Builder permitDiskWrites() { return disable(DETECT_THREAD_DISK_WRITE); } /** - * Detect calls to {@link Runtime#gc()}. + * Detects calls to {@link Runtime#gc()}. */ public @NonNull Builder detectExplicitGc() { return enable(DETECT_THREAD_EXPLICIT_GC); } /** - * Disable detection of calls to {@link Runtime#gc()}. + * Disables detection of calls to {@link Runtime#gc()}. */ public @NonNull Builder permitExplicitGc() { return disable(DETECT_THREAD_EXPLICIT_GC); } /** - * Show an annoying dialog to the developer on detected violations, rate-limited to be + * Shows an annoying dialog to the developer on detected violations, rate-limited to be * only a little annoying. */ public @NonNull Builder penaltyDialog() { @@ -643,7 +643,7 @@ public final class StrictMode { } /** - * Crash the whole process on violation. This penalty runs at the end of all enabled + * Crashes the whole process on violation. This penalty runs at the end of all enabled * penalties so you'll still get see logging or other violations before the process * dies. * @@ -655,7 +655,7 @@ public final class StrictMode { } /** - * Crash the whole process on any network usage. Unlike {@link #penaltyDeath}, this + * Crashes the whole process on any network usage. Unlike {@link #penaltyDeath}, this * penalty runs <em>before</em> anything else. You must still have called {@link * #detectNetwork} to enable this. * @@ -665,18 +665,18 @@ public final class StrictMode { return enable(PENALTY_DEATH_ON_NETWORK); } - /** Flash the screen during a violation. */ + /** Flashes the screen during a violation. */ public @NonNull Builder penaltyFlashScreen() { return enable(PENALTY_FLASH); } - /** Log detected violations to the system log. */ + /** Logs detected violations to the system log. */ public @NonNull Builder penaltyLog() { return enable(PENALTY_LOG); } /** - * Enable detected violations log a stacktrace and timing data to the {@link + * Enables detected violations log a stacktrace and timing data to the {@link * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform * integrators doing beta user field data collection. */ @@ -685,7 +685,7 @@ public final class StrictMode { } /** - * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified + * Calls #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified * executor every violation. */ public @NonNull Builder penaltyListener( @@ -715,7 +715,7 @@ public final class StrictMode { } /** - * Construct the ThreadPolicy instance. + * Constructs the ThreadPolicy instance. * * <p>Note: if no penalties are enabled before calling <code>build</code>, {@link * #penaltyLog} is implicitly set. @@ -805,7 +805,7 @@ public final class StrictMode { mMask = 0; } - /** Build upon an existing VmPolicy. */ + /** Builds upon an existing VmPolicy. */ public Builder(VmPolicy base) { mMask = base.mask; mClassInstanceLimitNeedCow = true; @@ -815,7 +815,7 @@ public final class StrictMode { } /** - * Set an upper bound on how many instances of a class can be in memory at once. Helps + * Sets an upper bound on how many instances of a class can be in memory at once. Helps * to prevent object leaks. */ public @NonNull Builder setClassInstanceLimit(Class klass, int instanceLimit) { @@ -838,7 +838,7 @@ public final class StrictMode { return this; } - /** Detect leaks of {@link android.app.Activity} subclasses. */ + /** Detects leaks of {@link android.app.Activity} subclasses. */ public @NonNull Builder detectActivityLeaks() { return enable(DETECT_VM_ACTIVITY_LEAKS); } @@ -852,7 +852,7 @@ public final class StrictMode { } /** - * Detect reflective usage of APIs that are not part of the public Android SDK. + * Detects reflective usage of APIs that are not part of the public Android SDK. * * <p>Note that any non-SDK APIs that this processes accesses before this detection is * enabled may not be detected. To ensure that all such API accesses are detected, @@ -863,7 +863,7 @@ public final class StrictMode { } /** - * Permit reflective usage of APIs that are not part of the public Android SDK. Note + * Permits reflective usage of APIs that are not part of the public Android SDK. Note * that this <b>only</b> affects {@code StrictMode}, the underlying runtime may * continue to restrict or warn on access to methods that are not part of the * public SDK. @@ -873,7 +873,7 @@ public final class StrictMode { } /** - * Detect everything that's potentially suspect. + * Detects everything that's potentially suspect. * * <p>In the Honeycomb release this includes leaks of SQLite cursors, Activities, and * other closable objects but will likely expand in future releases. @@ -924,8 +924,8 @@ public final class StrictMode { } /** - * Detect when an {@link android.database.sqlite.SQLiteCursor} or other SQLite object is - * finalized without having been closed. + * Detects when an {@link android.database.sqlite.SQLiteCursor} or other SQLite + * object is finalized without having been closed. * * <p>You always want to explicitly close your SQLite cursors to avoid unnecessary * database contention and temporary memory leaks. @@ -935,8 +935,8 @@ public final class StrictMode { } /** - * Detect when an {@link java.io.Closeable} or other object with an explicit termination - * method is finalized without having been closed. + * Detects when an {@link java.io.Closeable} or other object with an explicit + * termination method is finalized without having been closed. * * <p>You always want to explicitly close such objects to avoid unnecessary resources * leaks. @@ -946,16 +946,16 @@ public final class StrictMode { } /** - * Detect when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked during - * {@link Context} teardown. + * Detects when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked + * during {@link Context} teardown. */ public @NonNull Builder detectLeakedRegistrationObjects() { return enable(DETECT_VM_REGISTRATION_LEAKS); } /** - * Detect when the calling application exposes a {@code file://} {@link android.net.Uri} - * to another app. + * Detects when the calling application exposes a {@code file://} + * {@link android.net.Uri} to another app. * * <p>This exposure is discouraged since the receiving app may not have access to the * shared path. For example, the receiving app may not have requested the {@link @@ -973,9 +973,9 @@ public final class StrictMode { } /** - * Detect any network traffic from the calling app which is not wrapped in SSL/TLS. This - * can help you detect places that your app is inadvertently sending cleartext data - * across the network. + * Detects any network traffic from the calling app which is not wrapped in SSL/TLS. + * This can help you detect places that your app is inadvertently sending cleartext + * data across the network. * * <p>Using {@link #penaltyDeath()} or {@link #penaltyDeathOnCleartextNetwork()} will * block further traffic on that socket to prevent accidental data leakage, in addition @@ -992,7 +992,7 @@ public final class StrictMode { } /** - * Detect when the calling application sends a {@code content://} {@link + * Detects when the calling application sends a {@code content://} {@link * android.net.Uri} to another app without setting {@link * Intent#FLAG_GRANT_READ_URI_PERMISSION} or {@link * Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. @@ -1008,7 +1008,7 @@ public final class StrictMode { } /** - * Detect any sockets in the calling app which have not been tagged using {@link + * Detects any sockets in the calling app which have not been tagged using {@link * TrafficStats}. Tagging sockets can help you investigate network usage inside your * app, such as a narrowing down heavy usage to a specific library or component. * @@ -1028,7 +1028,7 @@ public final class StrictMode { } /** - * Detect any implicit reliance on Direct Boot automatic filtering + * Detects any implicit reliance on Direct Boot automatic filtering * of {@link PackageManager} values. Violations are only triggered * when implicit calls are made while the user is locked. * <p> @@ -1051,7 +1051,7 @@ public final class StrictMode { } /** - * Detect access to filesystem paths stored in credential protected + * Detects access to filesystem paths stored in credential protected * storage areas while the user is locked. * <p> * When a user is locked, credential protected storage is @@ -1072,7 +1072,7 @@ public final class StrictMode { } /** - * Detect attempts to invoke a method on a {@link Context} that is not suited for such + * Detects attempts to invoke a method on a {@link Context} that is not suited for such * operation. * <p>An example of this is trying to obtain an instance of UI service (e.g. * {@link android.view.WindowManager}) from a non-visual {@link Context}. This is not @@ -1086,7 +1086,7 @@ public final class StrictMode { } /** - * Disable detection of incorrect context use. + * Disables detection of incorrect context use. * * @see #detectIncorrectContextUse() * @@ -1098,7 +1098,7 @@ public final class StrictMode { } /** - * Detect when your app sends an unsafe {@link Intent}. + * Detects when your app sends an unsafe {@link Intent}. * <p> * Violations may indicate security vulnerabilities in the design of * your app, where a malicious app could trick you into granting @@ -1139,7 +1139,7 @@ public final class StrictMode { } /** - * Permit your app to launch any {@link Intent} which originated + * Permits your app to launch any {@link Intent} which originated * from outside your app. * <p> * Disabling this check is <em>strongly discouraged</em>, as @@ -1214,13 +1214,13 @@ public final class StrictMode { return enable(PENALTY_DEATH_ON_FILE_URI_EXPOSURE); } - /** Log detected violations to the system log. */ + /** Logs detected violations to the system log. */ public @NonNull Builder penaltyLog() { return enable(PENALTY_LOG); } /** - * Enable detected violations log a stacktrace and timing data to the {@link + * Enables detected violations log a stacktrace and timing data to the {@link * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform * integrators doing beta user field data collection. */ @@ -1229,7 +1229,7 @@ public final class StrictMode { } /** - * Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation. + * Calls #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation. */ public @NonNull Builder penaltyListener( @NonNull Executor executor, @NonNull OnVmViolationListener listener) { @@ -1258,7 +1258,7 @@ public final class StrictMode { } /** - * Construct the VmPolicy instance. + * Constructs the VmPolicy instance. * * <p>Note: if no penalties are enabled before calling <code>build</code>, {@link * #penaltyLog} is implicitly set. @@ -1474,7 +1474,7 @@ public final class StrictMode { } /** - * Determine if the given app is "bundled" as part of the system image. These bundled apps are + * Determines if the given app is "bundled" as part of the system image. These bundled apps are * developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to * chase any {@link StrictMode} regressions by enabling detection when running on {@link * Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds. @@ -1512,7 +1512,7 @@ public final class StrictMode { } /** - * Initialize default {@link ThreadPolicy} for the current thread. + * Initializes default {@link ThreadPolicy} for the current thread. * * @hide */ @@ -1547,7 +1547,7 @@ public final class StrictMode { } /** - * Initialize default {@link VmPolicy} for the current VM. + * Initializes default {@link VmPolicy} for the current VM. * * @hide */ @@ -2244,7 +2244,7 @@ public final class StrictMode { } /** - * Enable the recommended StrictMode defaults, with violations just being logged. + * Enables the recommended StrictMode defaults, with violations just being logged. * * <p>This catches disk and network access on the main thread, as well as leaked SQLite cursors * and unclosed resources. This is simply a wrapper around {@link #setVmPolicy} and {@link @@ -2545,7 +2545,7 @@ public final class StrictMode { private static final SparseLongArray sRealLastVmViolationTime = new SparseLongArray(); /** - * Clamp the given map by removing elements with timestamp older than the given retainSince. + * Clamps the given map by removing elements with timestamp older than the given retainSince. */ private static void clampViolationTimeMap(final @NonNull SparseLongArray violationTime, final long retainSince) { @@ -2812,7 +2812,7 @@ public final class StrictMode { }; /** - * Enter a named critical span (e.g. an animation) + * Enters a named critical span (e.g. an animation) * * <p>The name is an arbitary label (or tag) that will be applied to any strictmode violation * that happens while this span is active. You must call finish() on the span when done. @@ -3056,7 +3056,7 @@ public final class StrictMode { /** If this is a instance count violation, the number of instances in memory, else -1. */ public long numInstances = -1; - /** Create an instance of ViolationInfo initialized from an exception. */ + /** Creates an instance of ViolationInfo initialized from an exception. */ ViolationInfo(Violation tr, int penaltyMask) { this.mViolation = tr; this.mPenaltyMask = penaltyMask; @@ -3131,8 +3131,8 @@ public final class StrictMode { } /** - * Add a {@link Throwable} from the current process that caused the underlying violation. We - * only preserve the stack trace elements. + * Adds a {@link Throwable} from the current process that caused the underlying violation. + * We only preserve the stack trace elements. * * @hide */ @@ -3160,14 +3160,14 @@ public final class StrictMode { return result; } - /** Create an instance of ViolationInfo initialized from a Parcel. */ + /** Creates an instance of ViolationInfo initialized from a Parcel. */ @UnsupportedAppUsage public ViolationInfo(Parcel in) { this(in, false); } /** - * Create an instance of ViolationInfo initialized from a Parcel. + * Creates an instance of ViolationInfo initialized from a Parcel. * * @param unsetGatheringBit if true, the caller is the root caller and the gathering penalty * should be removed. @@ -3203,7 +3203,7 @@ public final class StrictMode { tags = in.readStringArray(); } - /** Save a ViolationInfo instance to a parcel. */ + /** Saves a ViolationInfo instance to a parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeSerializable(mViolation); @@ -3248,7 +3248,7 @@ public final class StrictMode { } } - /** Dump a ViolationInfo instance to a Printer. */ + /** Dumps a ViolationInfo instance to a Printer. */ public void dump(Printer pw, String prefix) { pw.println(prefix + "stackTrace: " + getStackTrace()); pw.println(prefix + "penalty: " + mPenaltyMask); diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 6a4932211f27..ea9997b8e5f4 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,20 @@ 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" +} 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/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/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java index 1fe06d474803..66d64d711e58 100644 --- a/core/java/android/view/HapticFeedbackConstants.java +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -176,7 +176,7 @@ public class HapticFeedbackConstants { /** * The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the - * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by + * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by * moving back past the threshold. This constant indicates that the user's motion has just * passed the threshold for the action to be activated on release. * @@ -186,7 +186,7 @@ public class HapticFeedbackConstants { /** * The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the - * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by + * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by * moving back past the threshold. This constant indicates that the user's motion has just * re-crossed back "under" the threshold for the action to be activated, meaning the gesture is * currently in a cancelled state. diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 78773529294a..acbd95bf6810 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -212,8 +212,7 @@ public class InsetsSourceControl implements Parcelable { && mInitiallyVisible == that.mInitiallyVisible && mSurfacePosition.equals(that.mSurfacePosition) && mInsetsHint.equals(that.mInsetsHint) - && mSkipAnimationOnce == that.mSkipAnimationOnce - && Objects.equals(mImeStatsToken, that.mImeStatsToken); + && mSkipAnimationOnce == that.mSkipAnimationOnce; } @Override 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/View.java b/core/java/android/view/View.java index e49eec69fff5..5ee229f87a1e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -30,6 +30,7 @@ import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; +import static android.view.accessibility.Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS; import static android.view.accessibility.Flags.FLAG_SUPPLEMENTAL_DESCRIPTION; import static android.view.accessibility.Flags.removeChildHoverCheckForTouchExploration; import static android.view.accessibility.Flags.supplementalDescription; @@ -42,6 +43,7 @@ import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_H import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API; import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY; import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; +import static android.view.flags.Flags.calculateBoundsInParentFromBoundsInScreen; import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout; import static android.view.flags.Flags.sensitiveContentAppProtection; import static android.view.flags.Flags.toolkitFrameRateAnimationBugfix25q1; @@ -970,6 +972,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static boolean sAlwaysRemeasureExactly = false; /** + * When true calculates the bounds in parent from bounds in screen relative to its parents. + * This addresses the deprecated API (setBoundsInParent) in Compose, which causes empty + * getBoundsInParent call for Compose apps. + */ + private static boolean sCalculateBoundsInParentFromBoundsInScreenFlagValue = false; + + /** * When true makes it possible to use onMeasure caches also when the force layout flag is * enabled. This helps avoiding multiple measures in the same frame with the same dimensions. */ @@ -2561,6 +2570,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly(); sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision(); + sCalculateBoundsInParentFromBoundsInScreenFlagValue = + calculateBoundsInParentFromBoundsInScreen(); sUseMeasureCacheDuringForceLayoutFlagValue = enableUseMeasureCacheDuringForceLayout(); } @@ -8941,44 +8952,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT} - * {@link AccessibilityEvent} to suggest that an accessibility service announce the - * specified text to its users. - * <p> - * Note: The event generated with this API carries no semantic meaning, and is appropriate only - * in exceptional situations. Apps can generally achieve correct behavior for accessibility by - * accurately supplying the semantics of their UI. - * They should not need to specify what exactly is announced to users. + * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT} {@link + * AccessibilityEvent} to suggest that an accessibility service announce the specified text to + * its users. * - * <p> - * In general, only announce transitions and don't generate a confirmation message for simple - * actions like a button press. Label your controls concisely and precisely instead, and for - * significant UI changes like window changes, use - * {@link android.app.Activity#setTitle(CharSequence)} and - * {@link #setAccessibilityPaneTitle(CharSequence)}. + * <p>Note: The event generated with this API carries no semantic meaning, and accessibility + * services may choose to ignore it. Apps that accurately supply accessibility with the + * semantics of their UI should not need to specify what exactly is announced. * - * <p> - * Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical + * <p>In general, do not attempt to generate announcements as confirmation message for simple + * actions like a button press. Label your controls concisely and precisely instead. + * + * <p>To convey significant UI changes like window changes, use {@link + * android.app.Activity#setTitle(CharSequence)} and {@link + * #setAccessibilityPaneTitle(CharSequence)}. + * + * <p>Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical * views within the user interface. These should still be used sparingly as they may generate * announcements every time a View is updated. * - * <p> - * For notifying users about errors, such as in a login screen with text that displays an - * "incorrect password" notification, that view should send an AccessibilityEvent of type - * {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set - * {@link AccessibilityNodeInfo#setError(CharSequence)} instead. Custom widgets should expose - * error-setting methods that support accessibility automatically. For example, instead of - * explicitly sending this event when using a TextView, use - * {@link android.widget.TextView#setError(CharSequence)}. - * - * <p> - * Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the + * <p>Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the * user interface. While a live region may send different types of events generated by the view, * state description will send {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events of * type {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}. * + * <p>For notifying users about errors, such as in a login screen with text that displays an + * "incorrect password" notification, set {@link AccessibilityNodeInfo#setError(CharSequence)} + * and dispatch an {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with a change + * type of {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR}, instead. Some widgets may + * expose methods that convey error states to accessibility automatically, such as {@link + * android.widget.TextView#setError(CharSequence)}, which manages these accessibility semantics + * and event dispatch for callers. + * + * @deprecated Use one of the methods described in the documentation above to semantically + * describe UI instead of using an announcement, as accessibility services may choose to + * ignore events dispatched with this method. * @param text The announcement text. */ + @FlaggedApi(FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS) + @Deprecated public void announceForAccessibility(CharSequence text) { if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) { AccessibilityEvent event = AccessibilityEvent.obtain( @@ -9806,7 +9818,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, structure.setChildCount(1); final ViewStructure root = structure.newChild(0); if (info != null) { - populateVirtualStructure(root, provider, info, forAutofill); + populateVirtualStructure(root, provider, info, null, forAutofill); info.recycle(); } else { Log.w(AUTOFILL_LOG_TAG, "AccessibilityNodeInfo is null."); @@ -11105,11 +11117,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private void populateVirtualStructure(ViewStructure structure, AccessibilityNodeProvider provider, AccessibilityNodeInfo info, - boolean forAutofill) { + @Nullable AccessibilityNodeInfo parentInfo, boolean forAutofill) { structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()), null, null, info.getViewIdResourceName()); Rect rect = structure.getTempRect(); - info.getBoundsInParent(rect); + // The bounds in parent for Jetpack Compose views aren't set as setBoundsInParent is + // deprecated, and only setBoundsInScreen is called. + // The bounds in parent can be calculated by diff'ing the child view's bounds in screen with + // the parent's. + if (sCalculateBoundsInParentFromBoundsInScreenFlagValue) { + getBoundsInParent(info, parentInfo, rect); + } else { + info.getBoundsInParent(rect); + } structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height()); structure.setVisibility(VISIBLE); structure.setEnabled(info.isEnabled()); @@ -11193,13 +11213,32 @@ public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i))); if (cinfo != null) { ViewStructure child = structure.newChild(i); - populateVirtualStructure(child, provider, cinfo, forAutofill); + populateVirtualStructure(child, provider, cinfo, info, forAutofill); cinfo.recycle(); } } } } + private void getBoundsInParent(@NonNull AccessibilityNodeInfo info, + @Nullable AccessibilityNodeInfo parentInfo, @NonNull Rect rect) { + info.getBoundsInParent(rect); + // Fallback to calculate bounds in parent by diffing the bounds in + // screen if it's all 0. + if ((rect.left | rect.top | rect.right | rect.bottom) == 0) { + if (parentInfo != null) { + Rect parentBoundsInScreen = parentInfo.getBoundsInScreen(); + Rect boundsInScreen = info.getBoundsInScreen(); + rect.set(boundsInScreen.left - parentBoundsInScreen.left, + boundsInScreen.top - parentBoundsInScreen.top, + boundsInScreen.right - parentBoundsInScreen.left, + boundsInScreen.bottom - parentBoundsInScreen.top); + } else { + info.getBoundsInScreen(rect); + } + } + } + /** * Dispatch creation of {@link ViewStructure} down the hierarchy. The default * implementation calls {@link #onProvideStructure} and diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 5b39f62db261..b4b0687eb498 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1426,6 +1426,31 @@ public interface WindowManager extends ViewManager { "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE"; /** + * Activity-level {@link android.content.pm.PackageManager.Property PackageManager.Property} + * that specifies whether this activity can declare or request + * {@link android.R.attr#screenOrientation fixed orientation}, + * {@link android.R.attr#minAspectRatio max aspect ratio}, + * {@link android.R.attr#maxAspectRatio min aspect ratio} + * {@link android.R.attr#resizeableActivity unresizable} on large screen devices with the + * ignore orientation request display setting enabled since Android 16 (API level 36) or higher. + * + * <p>The default value is {@code false}. + * + * <p><b>Syntax:</b> + * <pre> + * <activity> + * <property + * android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY" + * android:value="true"/> + * </activity> + * </pre> + * @hide + */ + // TODO(b/357141415): Make this public API. + String PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY = + "android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY"; + + /** * @hide */ public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array"; diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index c690787e4a33..0dfaf4149ce5 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -563,10 +563,13 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par /** * Represents the event of an application making an announcement. - * <p> - * In general, follow the practices described in - * {@link View#announceForAccessibility(CharSequence)}. + * + * @deprecated Use one of the semantic alternative methods described in the documentation of + * {@link View#announceForAccessibility(CharSequence)} instead of using this event, as + * accessibility services may choose to ignore dispatch of this event type. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS) + @Deprecated public static final int TYPE_ANNOUNCEMENT = 1 << 14; /** diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index c07da410c7f9..7177ef330f06 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -85,6 +85,13 @@ flag { flag { namespace: "accessibility" + name: "deprecate_accessibility_announcement_apis" + description: "Controls the deprecation of platform APIs related to disruptive accessibility announcements" + bug: "376727542" +} + +flag { + namespace: "accessibility" name: "fix_merged_content_change_event_v2" description: "Fixes event type and source of content change event merged in ViewRootImpl" bug: "277305460" diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index 2ca62a0725df..dd32d57bd650 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -221,6 +221,7 @@ public interface ImeTracker { PHASE_WM_INVOKING_IME_REQUESTED_LISTENER, PHASE_CLIENT_ALREADY_HIDDEN, PHASE_CLIENT_VIEW_HANDLER_AVAILABLE, + PHASE_SERVER_UPDATE_CLIENT_VISIBILITY, }) @Retention(RetentionPolicy.SOURCE) @interface Phase {} @@ -430,6 +431,11 @@ public interface ImeTracker { * continue without. */ int PHASE_CLIENT_VIEW_HANDLER_AVAILABLE = ImeProtoEnums.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE; + /** + * ImeInsetsSourceProvider sets the reported visibility of the caller/client window (either the + * app or the RemoteInsetsControlTarget). + */ + int PHASE_SERVER_UPDATE_CLIENT_VISIBILITY = ImeProtoEnums.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY; /** * Called when an IME request is started. 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/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/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig index fd5de91c80ca..b2f125dd2821 100644 --- a/core/java/android/window/flags/responsible_apis.aconfig +++ b/core/java/android/window/flags/responsible_apis.aconfig @@ -16,13 +16,6 @@ flag { } flag { - name: "bal_show_toasts" - namespace: "responsible_apis" - description: "Enable toasts to indicate (potential) BAL blocking." - bug: "308059069" -} - -flag { name: "bal_show_toasts_blocked" namespace: "responsible_apis" description: "Enable toasts to indicate actual BAL blocking." @@ -64,14 +57,6 @@ flag { bug: "339720406" } -# replaced by bal_strict_mode_ro -flag { - name: "bal_strict_mode" - namespace: "responsible_apis" - description: "Strict mode flag" - bug: "324089586" -} - flag { name: "bal_strict_mode_ro" namespace: "responsible_apis" diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 460df3103488..392c307de7ba 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -13,14 +13,6 @@ flag { flag { namespace: "window_surfaces" - name: "explicit_refresh_rate_hints" - description: "Performance related hints during transitions" - is_fixed_read_only: true - bug: "300019131" -} - -flag { - namespace: "window_surfaces" name: "delete_capture_display" description: "Delete uses of ScreenCapture#captureDisplay" is_fixed_read_only: true diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java index de3edeb22a40..15736ed3f4e1 100644 --- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java +++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java @@ -18,14 +18,13 @@ package com.android.internal.os; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import dalvik.annotation.optimization.CriticalNative; - import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -255,8 +254,8 @@ public class KernelSingleUidTimeReader { * the delta in the supplied array container. */ public void addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs, - LongArrayMultiStateCounter.LongArrayContainer deltaContainer) { - mInjector.addDelta(uid, counter, timestampMs, deltaContainer); + long[] delta) { + mInjector.addDelta(uid, counter, timestampMs, delta); } @VisibleForTesting @@ -274,15 +273,13 @@ public class KernelSingleUidTimeReader { * The delta is also returned via the optional deltaOut parameter. */ public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs, - LongArrayMultiStateCounter.LongArrayContainer deltaOut) { - return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs, - deltaOut != null ? deltaOut.mNativeObject : 0); + long[] deltaOut) { + return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs, deltaOut); } - @CriticalNative private static native boolean addDeltaFromBpf(int uid, long longArrayMultiStateCounterNativePointer, long timestampMs, - long longArrayContainerNativePointer); + @Nullable long[] deltaOut); /** * Used for testing. @@ -291,14 +288,14 @@ public class KernelSingleUidTimeReader { */ public boolean addDeltaForTest(int uid, LongArrayMultiStateCounter counter, long timestampMs, long[][] timeInFreqDataNanos, - LongArrayMultiStateCounter.LongArrayContainer deltaOut) { + long[] deltaOut) { return addDeltaForTest(uid, counter.mNativeObject, timestampMs, timeInFreqDataNanos, - deltaOut != null ? deltaOut.mNativeObject : 0); + deltaOut); } private static native boolean addDeltaForTest(int uid, long longArrayMultiStateCounterNativePointer, long timestampMs, - long[][] timeInFreqDataNanos, long longArrayContainerNativePointer); + long[][] timeInFreqDataNanos, long[] deltaOut); } @VisibleForTesting diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index 489721fbc10e..b3480ab92f46 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -30,9 +30,6 @@ import dalvik.annotation.optimization.FastNative; import libcore.util.NativeAllocationRegistry; -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicReference; - /** * Performs per-state counting of multi-element values over time. The class' behavior is illustrated * by this example: @@ -44,15 +41,14 @@ import java.util.concurrent.atomic.AtomicReference; * counter.setState(1, 1000); * * // At 3000 ms, the tracked values are updated to {30, 300} - * arrayContainer.setValues(new long[]{{30, 300}}; - * counter.updateValues(arrayContainer, 3000); + * counter.updateValues(arrayContainer, new long[]{{30, 300}, 3000); * * // The values are distributed between states 0 and 1 according to the time * // spent in those respective states. In this specific case, 1000 and 2000 ms. - * counter.getValues(arrayContainer, 0); - * // arrayContainer now has values {10, 100} - * counter.getValues(arrayContainer, 1); - * // arrayContainer now has values {20, 200} + * counter.getCounts(array, 0); + * // array now has values {10, 100} + * counter.getCounts(array, 1); + * // array now has values {20, 200} * </pre> * * The tracked values are expected to increase monotonically. @@ -62,110 +58,7 @@ import java.util.concurrent.atomic.AtomicReference; @RavenwoodKeepWholeClass @RavenwoodRedirectionClass("LongArrayMultiStateCounter_host") public final class LongArrayMultiStateCounter implements Parcelable { - - /** - * Container for a native equivalent of a long[]. - */ - @RavenwoodKeepWholeClass - @RavenwoodRedirectionClass("LongArrayContainer_host") - public static class LongArrayContainer { - private static NativeAllocationRegistry sRegistry; - - // Visible to other objects in this package so that it can be passed to @CriticalNative - // methods. - final long mNativeObject; - private final int mLength; - - public LongArrayContainer(int length) { - mLength = length; - mNativeObject = native_init(length); - registerNativeAllocation(); - } - - @RavenwoodReplace - private void registerNativeAllocation() { - if (sRegistry == null) { - synchronized (LongArrayMultiStateCounter.class) { - if (sRegistry == null) { - sRegistry = NativeAllocationRegistry.createMalloced( - LongArrayContainer.class.getClassLoader(), native_getReleaseFunc()); - } - } - } - sRegistry.registerNativeAllocation(this, mNativeObject); - } - - private void registerNativeAllocation$ravenwood() { - // No-op under ravenwood - } - - /** - * Copies the supplied values into the underlying native array. - */ - public void setValues(long[] array) { - if (array.length != mLength) { - throw new IllegalArgumentException( - "Invalid array length: " + array.length + ", expected: " + mLength); - } - native_setValues(mNativeObject, array); - } - - /** - * Copies the underlying native array values to the supplied array. - */ - public void getValues(long[] array) { - if (array.length != mLength) { - throw new IllegalArgumentException( - "Invalid array length: " + array.length + ", expected: " + mLength); - } - native_getValues(mNativeObject, array); - } - - /** - * Combines contained values into a smaller array by aggregating them - * according to an index map. - */ - public boolean combineValues(long[] array, int[] indexMap) { - if (indexMap.length != mLength) { - throw new IllegalArgumentException( - "Wrong index map size " + indexMap.length + ", expected " + mLength); - } - return native_combineValues(mNativeObject, array, indexMap); - } - - @Override - public String toString() { - final long[] array = new long[mLength]; - getValues(array); - return Arrays.toString(array); - } - - @CriticalNative - @RavenwoodRedirect - private static native long native_init(int length); - - @CriticalNative - @RavenwoodRedirect - private static native long native_getReleaseFunc(); - - @FastNative - @RavenwoodRedirect - private static native void native_setValues(long nativeObject, long[] array); - - @FastNative - @RavenwoodRedirect - private static native void native_getValues(long nativeObject, long[] array); - - @FastNative - @RavenwoodRedirect - private static native boolean native_combineValues(long nativeObject, long[] array, - int[] indexMap); - } - private static volatile NativeAllocationRegistry sRegistry; - private static final AtomicReference<LongArrayContainer> sTmpArrayContainer = - new AtomicReference<>(); - private final int mStateCount; private final int mLength; @@ -257,41 +150,14 @@ public final class LongArrayMultiStateCounter implements Parcelable { throw new IllegalArgumentException( "Invalid array length: " + values.length + ", expected: " + mLength); } - LongArrayContainer container = sTmpArrayContainer.getAndSet(null); - if (container == null || container.mLength != values.length) { - container = new LongArrayContainer(values.length); - } - container.setValues(values); - native_setValues(mNativeObject, state, container.mNativeObject); - sTmpArrayContainer.set(container); - } - - /** - * Sets the new values. The delta between the previously set values and these values - * is distributed among the state according to the time the object spent in those states - * since the previous call to updateValues. - */ - public void updateValues(long[] values, long timestampMs) { - LongArrayContainer container = sTmpArrayContainer.getAndSet(null); - if (container == null || container.mLength != values.length) { - container = new LongArrayContainer(values.length); - } - container.setValues(values); - updateValues(container, timestampMs); - sTmpArrayContainer.set(container); + native_setValues(mNativeObject, state, values); } /** * Adds the supplied values to the current accumulated values in the counter. */ public void incrementValues(long[] values, long timestampMs) { - LongArrayContainer container = sTmpArrayContainer.getAndSet(null); - if (container == null || container.mLength != values.length) { - container = new LongArrayContainer(values.length); - } - container.setValues(values); - native_incrementValues(mNativeObject, container.mNativeObject, timestampMs); - sTmpArrayContainer.set(container); + native_incrementValues(mNativeObject, values, timestampMs); } /** @@ -299,24 +165,23 @@ public final class LongArrayMultiStateCounter implements Parcelable { * is distributed among the state according to the time the object spent in those states * since the previous call to updateValues. */ - public void updateValues(LongArrayContainer longArrayContainer, long timestampMs) { - if (longArrayContainer.mLength != mLength) { + public void updateValues(long[] values, long timestampMs) { + if (values.length != mLength) { throw new IllegalArgumentException( - "Invalid array length: " + longArrayContainer.mLength + ", expected: " - + mLength); + "Invalid array length: " + values.length + ", expected: " + mLength); } - native_updateValues(mNativeObject, longArrayContainer.mNativeObject, timestampMs); + native_updateValues(mNativeObject, values, timestampMs); } /** * Adds the supplied values to the current accumulated values in the counter. */ - public void addCounts(LongArrayContainer counts) { - if (counts.mLength != mLength) { + public void addCounts(long[] counts) { + if (counts.length != mLength) { throw new IllegalArgumentException( - "Invalid array length: " + counts.mLength + ", expected: " + mLength); + "Invalid array length: " + counts.length + ", expected: " + mLength); } - native_addCounts(mNativeObject, counts.mNativeObject); + native_addCounts(mNativeObject, counts); } /** @@ -330,29 +195,15 @@ public final class LongArrayMultiStateCounter implements Parcelable { * Populates the array with the accumulated counts for the specified state. */ public void getCounts(long[] counts, int state) { - LongArrayContainer container = sTmpArrayContainer.getAndSet(null); - if (container == null || container.mLength != counts.length) { - container = new LongArrayContainer(counts.length); - } - getCounts(container, state); - container.getValues(counts); - sTmpArrayContainer.set(container); - } - - /** - * Populates longArrayContainer with the accumulated counts for the specified state. - */ - public void getCounts(LongArrayContainer longArrayContainer, int state) { if (state < 0 || state >= mStateCount) { throw new IllegalArgumentException( "State: " + state + ", outside the range: [0-" + mStateCount + "]"); } - if (longArrayContainer.mLength != mLength) { + if (counts.length != mLength) { throw new IllegalArgumentException( - "Invalid array length: " + longArrayContainer.mLength - + ", expected: " + mLength); + "Invalid array length: " + counts.length + ", expected: " + mLength); } - native_getCounts(mNativeObject, longArrayContainer.mNativeObject, state); + native_getCounts(mNativeObject, counts, state); } @Override @@ -370,18 +221,17 @@ public final class LongArrayMultiStateCounter implements Parcelable { return 0; } - public static final Creator<LongArrayMultiStateCounter> CREATOR = - new Creator<LongArrayMultiStateCounter>() { - @Override - public LongArrayMultiStateCounter createFromParcel(Parcel in) { - return new LongArrayMultiStateCounter(in); - } + public static final Creator<LongArrayMultiStateCounter> CREATOR = new Creator<>() { + @Override + public LongArrayMultiStateCounter createFromParcel(Parcel in) { + return new LongArrayMultiStateCounter(in); + } - @Override - public LongArrayMultiStateCounter[] newArray(int size) { - return new LongArrayMultiStateCounter[size]; - } - }; + @Override + public LongArrayMultiStateCounter[] newArray(int size) { + return new LongArrayMultiStateCounter[size]; + } + }; @CriticalNative @@ -406,34 +256,31 @@ public final class LongArrayMultiStateCounter implements Parcelable { private static native void native_copyStatesFrom(long nativeObjectTarget, long nativeObjectSource); - @CriticalNative + @FastNative @RavenwoodRedirect - private static native void native_setValues(long nativeObject, int state, - long longArrayContainerNativeObject); + private static native void native_setValues(long nativeObject, int state, long[] values); - @CriticalNative + @FastNative @RavenwoodRedirect - private static native void native_updateValues(long nativeObject, - long longArrayContainerNativeObject, long timestampMs); + private static native void native_updateValues(long nativeObject, long[] values, + long timestampMs); - @CriticalNative + @FastNative @RavenwoodRedirect - private static native void native_incrementValues(long nativeObject, - long longArrayContainerNativeObject, long timestampMs); + private static native void native_incrementValues(long nativeObject, long[] values, + long timestampMs); - @CriticalNative + @FastNative @RavenwoodRedirect - private static native void native_addCounts(long nativeObject, - long longArrayContainerNativeObject); + private static native void native_addCounts(long nativeObject, long[] counts); @CriticalNative @RavenwoodRedirect private static native void native_reset(long nativeObject); - @CriticalNative + @FastNative @RavenwoodRedirect - private static native void native_getCounts(long nativeObject, - long longArrayContainerNativeObject, int state); + private static native void native_getCounts(long nativeObject, long[] counts, int state); @FastNative @RavenwoodRedirect diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java index 30b160ab161b..a69d2e4f4dca 100644 --- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java +++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java @@ -28,19 +28,9 @@ import android.ravenwood.annotation.RavenwoodReplace; public final class RavenwoodEnvironment { public static final String TAG = "RavenwoodEnvironment"; - private static final RavenwoodEnvironment sInstance; - private static final Workaround sWorkaround; + private static RavenwoodEnvironment sInstance = new RavenwoodEnvironment(); - private RavenwoodEnvironment() { - } - - static { - sInstance = new RavenwoodEnvironment(); - sWorkaround = new Workaround(); - ensureRavenwoodInitialized(); - } - - public static RuntimeException notSupportedOnDevice() { + private static RuntimeException notSupportedOnDevice() { return new UnsupportedOperationException("This method can only be used on Ravenwood"); } @@ -52,15 +42,6 @@ public final class RavenwoodEnvironment { } /** - * Initialize the ravenwood environment if it hasn't happened already, if running on Ravenwood. - * - * No-op if called on the device side. - */ - @RavenwoodRedirect - public static void ensureRavenwoodInitialized() { - } - - /** * USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment. * * <p>Using this allows code to behave differently on a real device and on Ravenwood, but @@ -91,38 +72,10 @@ public final class RavenwoodEnvironment { } /** - * See {@link Workaround}. It's only usable on Ravenwood. - */ - @RavenwoodReplace - public static Workaround workaround() { - throw notSupportedOnDevice(); - } - - private static Workaround workaround$ravenwood() { - return sWorkaround; - } - - /** * @return the "ravenwood-runtime" directory. */ @RavenwoodRedirect public String getRavenwoodRuntimePath() { throw notSupportedOnDevice(); } - - /** - * A set of APIs used to work around missing features on Ravenwood. Ideally, this class should - * be empty, and all its APIs should be able to be implemented properly. - */ - public static class Workaround { - Workaround() { - } - - /** - * @return whether the app's target SDK level is at least Q. - */ - public boolean isTargetSdkAtLeastQ() { - return true; - } - } } 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/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp index bea5ffe31da0..a5b5057ecbac 100644 --- a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp +++ b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp @@ -49,27 +49,26 @@ static jlongArray getUidCpuFreqTimeMs(JNIEnv *env, jclass, jint uid) { * to the supplied multi-state counter in accordance with the counter's state. */ static jboolean addCpuTimeInFreqDelta( - jint uid, jlong counterNativePtr, jlong timestampMs, + JNIEnv *env, jint uid, jlong counterNativePtr, jlong timestampMs, std::optional<std::vector<std::vector<uint64_t>>> timeInFreqDataNanos, - jlong deltaOutContainerNativePtr) { + jlongArray deltaOut) { if (!timeInFreqDataNanos) { return false; } - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(counterNativePtr); + auto counter = reinterpret_cast<battery::LongArrayMultiStateCounter *>(counterNativePtr); size_t s = 0; for (const auto &cluster : *timeInFreqDataNanos) s += cluster.size(); - std::vector<uint64_t> flattened; - flattened.reserve(s); - auto offset = flattened.begin(); + battery::Uint64ArrayRW flattened(s); + uint64_t *out = flattened.dataRW(); + auto offset = out; for (const auto &cluster : *timeInFreqDataNanos) { - flattened.insert(offset, cluster.begin(), cluster.end()); + memcpy(offset, cluster.data(), cluster.size() * sizeof(uint64_t)); offset += cluster.size(); } for (size_t i = 0; i < s; ++i) { - flattened[i] /= NSEC_PER_MSEC; + out[i] /= NSEC_PER_MSEC; } if (s != counter->getCount(0).size()) { // Counter has at least one state ALOGE("Mismatch between eBPF data size (%d) and the counter size (%d)", (int)s, @@ -77,29 +76,32 @@ static jboolean addCpuTimeInFreqDelta( return false; } - const std::vector<uint64_t> &delta = counter->updateValue(flattened, timestampMs); - if (deltaOutContainerNativePtr) { - std::vector<uint64_t> *vector = - reinterpret_cast<std::vector<uint64_t> *>(deltaOutContainerNativePtr); - *vector = delta; + const battery::Uint64Array &delta = counter->updateValue(flattened, timestampMs); + if (deltaOut) { + ScopedLongArrayRW scopedArray(env, deltaOut); + uint64_t *array = reinterpret_cast<uint64_t *>(scopedArray.get()); + if (delta.data() != nullptr) { + memcpy(array, delta.data(), s * sizeof(uint64_t)); + } else { + memset(array, 0, s * sizeof(uint64_t)); + } } return true; } -static jboolean addDeltaFromBpf(jint uid, jlong counterNativePtr, jlong timestampMs, - jlong deltaOutContainerNativePtr) { - return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs, - android::bpf::getUidCpuFreqTimes(uid), deltaOutContainerNativePtr); +static jboolean addDeltaFromBpf(JNIEnv *env, jlong self, jint uid, jlong counterNativePtr, + jlong timestampMs, jlongArray deltaOut) { + return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs, + android::bpf::getUidCpuFreqTimes(uid), deltaOut); } static jboolean addDeltaForTest(JNIEnv *env, jclass, jint uid, jlong counterNativePtr, jlong timestampMs, jobjectArray timeInFreqDataNanos, - jlong deltaOutContainerNativePtr) { + jlongArray deltaOut) { if (!timeInFreqDataNanos) { - return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs, - std::optional<std::vector<std::vector<uint64_t>>>(), - deltaOutContainerNativePtr); + return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs, + std::optional<std::vector<std::vector<uint64_t>>>(), deltaOut); } std::vector<std::vector<uint64_t>> timeInFreqData; @@ -113,18 +115,16 @@ static jboolean addDeltaForTest(JNIEnv *env, jclass, jint uid, jlong counterNati } timeInFreqData.push_back(cluster); } - return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs, std::optional(timeInFreqData), - deltaOutContainerNativePtr); + return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs, + std::optional(timeInFreqData), deltaOut); } static const JNINativeMethod g_single_methods[] = { {"readBpfData", "(I)[J", (void *)getUidCpuFreqTimeMs}, - - // @CriticalNative - {"addDeltaFromBpf", "(IJJJ)Z", (void *)addDeltaFromBpf}, + {"addDeltaFromBpf", "(IJJ[J)Z", (void *)addDeltaFromBpf}, // Used for testing - {"addDeltaForTest", "(IJJ[[JJ)Z", (void *)addDeltaForTest}, + {"addDeltaForTest", "(IJJ[[J[J)Z", (void *)addDeltaForTest}, }; int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env) { diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp index b3c41dfe81a1..7ffe0ed7c6cd 100644 --- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp +++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp @@ -26,16 +26,40 @@ #include "core_jni_helpers.h" namespace android { +namespace battery { + +/** + * Implementation of Uint64Array that wraps a Java long[]. Since it uses the "critical" + * version of JNI array access (halting GC), any usage of this class must be extra quick. + */ +class JavaUint64Array : public Uint64Array { + JNIEnv *mEnv; + jlongArray mJavaArray; + uint64_t *mData; + +public: + JavaUint64Array(JNIEnv *env, jlongArray values) : Uint64Array(env->GetArrayLength(values)) { + mEnv = env; + mJavaArray = values; + mData = reinterpret_cast<uint64_t *>(mEnv->GetPrimitiveArrayCritical(mJavaArray, nullptr)); + } + + ~JavaUint64Array() override { + mEnv->ReleasePrimitiveArrayCritical(mJavaArray, mData, 0); + } + + const uint64_t *data() const override { + return mData; + } +}; static jlong native_init(jint stateCount, jint arrayLength) { - battery::LongArrayMultiStateCounter *counter = - new battery::LongArrayMultiStateCounter(stateCount, std::vector<uint64_t>(arrayLength)); + auto *counter = new LongArrayMultiStateCounter(stateCount, Uint64Array(arrayLength)); return reinterpret_cast<jlong>(counter); } static void native_dispose(void *nativePtr) { - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); + auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); delete counter; } @@ -44,80 +68,63 @@ static jlong native_getReleaseFunc() { } static void native_setEnabled(jlong nativePtr, jboolean enabled, jlong timestamp) { - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); + auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); counter->setEnabled(enabled, timestamp); } static void native_setState(jlong nativePtr, jint state, jlong timestamp) { - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); + auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); counter->setState(state, timestamp); } static void native_copyStatesFrom(jlong nativePtrTarget, jlong nativePtrSource) { - battery::LongArrayMultiStateCounter *counterTarget = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrTarget); - battery::LongArrayMultiStateCounter *counterSource = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrSource); + auto *counterTarget = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrTarget); + auto *counterSource = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrSource); counterTarget->copyStatesFrom(*counterSource); } -static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) { - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); - std::vector<uint64_t> *vector = - reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr); - - counter->setValue(state, *vector); +static void native_setValues(JNIEnv *env, jclass, jlong nativePtr, jint state, jlongArray values) { + auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); + counter->setValue(state, JavaUint64Array(env, values)); } -static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr, +static void native_updateValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray values, jlong timestamp) { - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); - std::vector<uint64_t> *vector = - reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr); - - counter->updateValue(*vector, timestamp); + auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); + counter->updateValue(JavaUint64Array(env, values), timestamp); } -static void native_incrementValues(jlong nativePtr, jlong longArrayContainerNativePtr, +static void native_incrementValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray values, jlong timestamp) { - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); - std::vector<uint64_t> *vector = - reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr); - - counter->incrementValue(*vector, timestamp); + auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); + counter->incrementValue(JavaUint64Array(env, values), timestamp); } -static void native_addCounts(jlong nativePtr, jlong longArrayContainerNativePtr) { - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); - std::vector<uint64_t> *vector = - reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr); - counter->addValue(*vector); +static void native_addCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values) { + auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); + counter->addValue(JavaUint64Array(env, values)); } static void native_reset(jlong nativePtr) { - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); + auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); counter->reset(); } -static void native_getCounts(jlong nativePtr, jlong longArrayContainerNativePtr, jint state) { - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); - std::vector<uint64_t> *vector = - reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr); - - *vector = counter->getCount(state); +static void native_getCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values, jint state) { + auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); + ScopedLongArrayRW scopedArray(env, values); + auto *data = counter->getCount(state).data(); + auto size = env->GetArrayLength(values); + auto *outData = scopedArray.get(); + if (data == nullptr) { + memset(outData, 0, size * sizeof(uint64_t)); + } else { + memcpy(outData, data, size * sizeof(uint64_t)); + } } static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) { - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); + auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); return env->NewStringUTF(counter->toString().c_str()); } @@ -137,20 +144,26 @@ static void throwWriteRE(JNIEnv *env, binder_status_t status) { static void native_writeToParcel(JNIEnv *env, jclass, jlong nativePtr, jobject jParcel, jint flags) { - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); + auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel)); uint16_t stateCount = counter->getStateCount(); THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), stateCount)); // LongArrayMultiStateCounter has at least state 0 - const std::vector<uint64_t> &anyState = counter->getCount(0); + const Uint64Array &anyState = counter->getCount(0); THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), anyState.size())); for (battery::state_t state = 0; state < stateCount; state++) { - THROW_AND_RETURN_ON_WRITE_ERROR( - ndk::AParcel_writeVector(parcel.get(), counter->getCount(state))); + const Uint64Array &value = counter->getCount(state); + if (value.data() == nullptr) { + THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeBool(parcel.get(), false)); + } else { + THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeBool(parcel.get(), true)); + for (size_t i = 0; i < anyState.size(); i++) { + THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeUint64(parcel.get(), value.data()[i])); + } + } } } @@ -183,40 +196,37 @@ static jlong native_initFromParcel(JNIEnv *env, jclass, jobject jParcel) { int32_t arrayLength; THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &arrayLength)); - auto counter = std::make_unique<battery::LongArrayMultiStateCounter>(stateCount, - std::vector<uint64_t>( - arrayLength)); - - std::vector<uint64_t> value; - value.reserve(arrayLength); - + auto counter = + std::make_unique<LongArrayMultiStateCounter>(stateCount, Uint64Array(arrayLength)); + Uint64ArrayRW array(arrayLength); for (battery::state_t state = 0; state < stateCount; state++) { - THROW_AND_RETURN_ON_READ_ERROR(ndk::AParcel_readVector(parcel.get(), &value)); - counter->setValue(state, value); + bool hasValues; + THROW_AND_RETURN_ON_READ_ERROR(AParcel_readBool(parcel.get(), &hasValues)); + if (hasValues) { + for (int i = 0; i < arrayLength; i++) { + THROW_AND_RETURN_ON_READ_ERROR( + AParcel_readUint64(parcel.get(), &(array.dataRW()[i]))); + } + counter->setValue(state, array); + } } return reinterpret_cast<jlong>(counter.release()); } static jint native_getStateCount(jlong nativePtr) { - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); + auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); return counter->getStateCount(); } static jint native_getArrayLength(jlong nativePtr) { - battery::LongArrayMultiStateCounter *counter = - reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); + auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); // LongArrayMultiStateCounter has at least state 0 - const std::vector<uint64_t> &anyState = counter->getCount(0); + const Uint64Array &anyState = counter->getCount(0); return anyState.size(); } -static jlong native_init_LongArrayContainer(jint length) { - return reinterpret_cast<jlong>(new std::vector<uint64_t>(length)); -} - static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = { // @CriticalNative {"native_init", "(II)J", (void *)native_init}, @@ -228,18 +238,18 @@ static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = { {"native_setState", "(JIJ)V", (void *)native_setState}, // @CriticalNative {"native_copyStatesFrom", "(JJ)V", (void *)native_copyStatesFrom}, - // @CriticalNative - {"native_setValues", "(JIJ)V", (void *)native_setValues}, - // @CriticalNative - {"native_updateValues", "(JJJ)V", (void *)native_updateValues}, - // @CriticalNative - {"native_incrementValues", "(JJJ)V", (void *)native_incrementValues}, - // @CriticalNative - {"native_addCounts", "(JJ)V", (void *)native_addCounts}, + // @FastNative + {"native_setValues", "(JI[J)V", (void *)native_setValues}, + // @FastNative + {"native_updateValues", "(J[JJ)V", (void *)native_updateValues}, + // @FastNative + {"native_incrementValues", "(J[JJ)V", (void *)native_incrementValues}, + // @FastNative + {"native_addCounts", "(J[J)V", (void *)native_addCounts}, // @CriticalNative {"native_reset", "(J)V", (void *)native_reset}, - // @CriticalNative - {"native_getCounts", "(JJI)V", (void *)native_getCounts}, + // @FastNative + {"native_getCounts", "(J[JI)V", (void *)native_getCounts}, // @FastNative {"native_toString", "(J)Ljava/lang/String;", (void *)native_toString}, // @FastNative @@ -252,91 +262,12 @@ static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = { {"native_getArrayLength", "(J)I", (void *)native_getArrayLength}, }; -/////////////////////// LongArrayMultiStateCounter.LongArrayContainer //////////////////////// - -static void native_dispose_LongArrayContainer(jlong nativePtr) { - std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr); - delete vector; -} - -static jlong native_getReleaseFunc_LongArrayContainer() { - return reinterpret_cast<jlong>(native_dispose_LongArrayContainer); -} - -static void native_setValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr, - jlongArray jarray) { - std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr); - ScopedLongArrayRO scopedArray(env, jarray); - const uint64_t *array = reinterpret_cast<const uint64_t *>(scopedArray.get()); - uint8_t size = scopedArray.size(); - - // Boundary checks are performed in the Java layer - std::copy(array, array + size, vector->data()); -} - -static void native_getValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr, - jlongArray jarray) { - std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr); - ScopedLongArrayRW scopedArray(env, jarray); - - // Boundary checks are performed in the Java layer - std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get()); -} - -static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr, - jlongArray jarray, jintArray jindexMap) { - std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr); - ScopedLongArrayRW scopedArray(env, jarray); - ScopedIntArrayRO scopedIndexMap(env, jindexMap); - - const uint64_t *data = vector->data(); - uint64_t *array = reinterpret_cast<uint64_t *>(scopedArray.get()); - const uint8_t size = scopedArray.size(); - - for (int i = 0; i < size; i++) { - array[i] = 0; - } - - bool nonZero = false; - for (size_t i = 0; i < vector->size(); i++) { - jint index = scopedIndexMap[i]; - if (index < 0 || index >= size) { - jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException", - "Index %d is out of bounds: [0, %d]", index, size - 1); - return false; - } - - if (data[i] != 0L) { - array[index] += data[i]; - nonZero = true; - } - } - - return nonZero; -} - -static const JNINativeMethod g_LongArrayContainer_methods[] = { - // @CriticalNative - {"native_init", "(I)J", (void *)native_init_LongArrayContainer}, - // @CriticalNative - {"native_getReleaseFunc", "()J", (void *)native_getReleaseFunc_LongArrayContainer}, - // @FastNative - {"native_setValues", "(J[J)V", (void *)native_setValues_LongArrayContainer}, - // @FastNative - {"native_getValues", "(J[J)V", (void *)native_getValues_LongArrayContainer}, - // @FastNative - {"native_combineValues", "(J[J[I)Z", (void *)native_combineValues_LongArrayContainer}, -}; +} // namespace battery int register_com_android_internal_os_LongArrayMultiStateCounter(JNIEnv *env) { // 0 represents success, thus "|" and not "&" return RegisterMethodsOrDie(env, "com/android/internal/os/LongArrayMultiStateCounter", - g_LongArrayMultiStateCounter_methods, - NELEM(g_LongArrayMultiStateCounter_methods)) | - RegisterMethodsOrDie(env, - "com/android/internal/os/LongArrayMultiStateCounter" - "$LongArrayContainer", - g_LongArrayContainer_methods, NELEM(g_LongArrayContainer_methods)); + battery::g_LongArrayMultiStateCounter_methods, + NELEM(battery::g_LongArrayMultiStateCounter_methods)); } - } // namespace android diff --git a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp index 56d3fbba9458..b3bfd0bc5e09 100644 --- a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp +++ b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp @@ -28,7 +28,7 @@ namespace android { namespace battery { -typedef battery::MultiStateCounter<int64_t> LongMultiStateCounter; +typedef battery::MultiStateCounter<int64_t, int64_t> LongMultiStateCounter; template <> bool LongMultiStateCounter::delta(const int64_t &previousValue, const int64_t &newValue, @@ -47,12 +47,6 @@ void LongMultiStateCounter::add(int64_t *value1, const int64_t &value2, const ui *value1 += value2; } } - -template <> -std::string LongMultiStateCounter::valueToString(const int64_t &v) const { - return std::to_string(v); -} - } // namespace battery static inline battery::LongMultiStateCounter *asLongMultiStateCounter(const jlong nativePtr) { diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 654d83c827c9..407790c89202 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -465,6 +465,7 @@ message WindowStateProto { repeated .android.graphics.RectProto unrestricted_keep_clear_areas = 46; repeated .android.view.InsetsSourceProto mergedLocalInsetsSources = 47; optional int32 requested_visible_types = 48; + optional .android.graphics.RectProto dim_bounds = 49; } message IdentifierProto { diff --git a/core/proto/android/service/appwidget.proto b/core/proto/android/service/appwidget.proto index 97350ef90eec..fb907196bfc7 100644 --- a/core/proto/android/service/appwidget.proto +++ b/core/proto/android/service/appwidget.proto @@ -20,6 +20,8 @@ package android.service.appwidget; option java_multiple_files = true; option java_outer_classname = "AppWidgetServiceProto"; +import "frameworks/base/core/proto/android/widget/remoteviews.proto"; + // represents the object holding the dump info of the app widget service message AppWidgetServiceDumpProto { repeated WidgetProto widgets = 1; // the array of bound widgets @@ -38,3 +40,14 @@ message WidgetProto { optional int32 maxHeight = 9; optional bool restoreCompleted = 10; } + +// represents a set of widget previews for a particular provider +message GeneratedPreviewsProto { + repeated Preview previews = 1; + + // represents a particular RemoteViews preview, which may be set for multiple categories + message Preview { + repeated int32 widget_categories = 1; + optional android.widget.RemoteViewsProto views = 2; + } +}
\ No newline at end of file diff --git a/core/res/res/drawable-watch/ic_lock_bugreport.xml b/core/res/res/drawable-watch/ic_lock_bugreport.xml index b664fe4f9c4e..35834be7b541 100644 --- a/core/res/res/drawable-watch/ic_lock_bugreport.xml +++ b/core/res/res/drawable-watch/ic_lock_bugreport.xml @@ -13,19 +13,6 @@ See the License for the specific language governing permissions and limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24.0dp" - android:height="24.0dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0" - android:tint="@android:color/white"> - <path - android:fillColor="@android:color/white" - android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/> - <path - android:fillColor="@android:color/white" - android:pathData="M10,14h4v2h-4z"/> - <path - android:fillColor="@android:color/white" - android:pathData="M10,10h4v2h-4z"/> +<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="M480.01,848.13Q412.37,848.13 354.6,814.34Q296.83,780.54 263.87,721.91L193.78,721.91Q175.97,721.91 163.92,709.86Q151.87,697.81 151.87,680Q151.87,662.19 163.92,650.14Q175.97,638.09 193.78,638.09L235.63,638.09Q232.81,619.12 232.34,600.16Q231.87,581.2 231.87,561.91L193.78,561.91Q175.97,561.91 163.92,549.86Q151.87,537.81 151.87,520Q151.87,502.19 163.92,490.14Q175.97,478.09 193.78,478.09L231.87,478.09Q231.87,458.8 232.34,439.84Q232.81,420.88 235.63,401.91L193.78,401.91Q175.97,401.91 163.92,389.86Q151.87,377.81 151.87,360Q151.87,342.19 163.92,330.14Q175.97,318.09 193.78,318.09L265.07,318.09Q278.11,294.13 295.97,273.77Q313.83,253.41 337.07,237.93L301.26,201.37Q289.54,189.65 289.66,172.32Q289.78,154.98 302.26,142.5Q313.98,130.78 331.7,130.78Q349.41,130.78 361.13,142.5L417.7,199.07Q447.13,188.63 477.92,188.39Q508.72,188.15 538.15,198.35L597.2,140.3Q608.79,128.59 626.19,128.59Q643.59,128.59 656.07,141.07Q667.78,152.78 667.78,170.5Q667.78,188.22 656.07,199.93L619.98,236.02Q644.41,251.98 663.51,273.03Q682.61,294.09 696.38,320L767.17,320Q784.41,320 796.27,331.86Q808.13,343.72 808.13,360.96Q808.13,378.2 796.27,390.05Q784.41,401.91 767.17,401.91L724.37,401.91Q727.19,420.88 727.66,439.84Q728.13,458.8 728.13,478.09L766.22,478.09Q784.03,478.09 796.08,490.14Q808.13,502.19 808.13,520Q808.13,537.81 796.08,549.86Q784.03,561.91 766.22,561.91L728.13,561.91Q728.13,581.2 727.51,600.12Q726.89,619.04 724.13,638.09L766.22,638.09Q784.03,638.09 796.08,650.14Q808.13,662.19 808.13,680Q808.13,697.81 796.08,709.86Q784.03,721.91 766.22,721.91L696.13,721.91Q663.17,780.54 605.42,814.34Q547.66,848.13 480.01,848.13ZM441.91,641.91L518.09,641.91Q535.9,641.91 547.95,629.86Q560,617.81 560,600Q560,582.19 547.95,570.14Q535.9,558.09 518.09,558.09L441.91,558.09Q424.1,558.09 412.05,570.14Q400,582.19 400,600Q400,617.81 412.05,629.86Q424.1,641.91 441.91,641.91ZM441.91,481.91L518.09,481.91Q535.9,481.91 547.95,469.86Q560,457.81 560,440Q560,422.19 547.95,410.14Q535.9,398.09 518.09,398.09L441.91,398.09Q424.1,398.09 412.05,410.14Q400,422.19 400,440Q400,457.81 412.05,469.86Q424.1,481.91 441.91,481.91Z"/> </vector> diff --git a/core/res/res/drawable-watch/ic_lock_power_off.xml b/core/res/res/drawable-watch/ic_lock_power_off.xml index b437a4b70cca..c42d7d2e0293 100644 --- a/core/res/res/drawable-watch/ic_lock_power_off.xml +++ b/core/res/res/drawable-watch/ic_lock_power_off.xml @@ -14,13 +14,6 @@ ~ limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24" - android:tint="@android:color/white"> - <path - android:fillColor="@android:color/white" - android:pathData="M11,2h2v10h-2zM18.37,5.64l-1.41,1.41c2.73,2.73 2.72,7.16 -0.01,9.89 -2.73,2.73 -7.17,2.73 -9.89,0.01 -2.73,-2.73 -2.74,-7.18 -0.01,-9.91l-1.41,-1.4c-3.51,3.51 -3.51,9.21 0.01,12.73 3.51,3.51 9.21,3.51 12.72,-0.01 3.51,-3.51 3.51,-9.2 0,-12.72z"/> -</vector> +<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="M480,888.13Q395.09,888.13 320.65,856.03Q246.22,823.93 191.14,768.86Q136.07,713.78 103.97,639.35Q71.87,564.91 71.87,480Q71.87,406.52 96.01,340.78Q120.15,275.04 162.91,222.33Q175.11,206.65 192.52,207.41Q209.93,208.17 222.61,219.37Q235.28,230.57 239.26,248.84Q243.24,267.11 227.8,287.22Q197.48,327.26 180.17,376.09Q162.87,424.91 162.87,480Q162.87,613.04 254.91,705.09Q346.96,797.13 480,797.13Q613.04,797.13 705.09,705.09Q797.13,613.04 797.13,480Q797.13,424.91 779.83,376.09Q762.52,327.26 732.2,287.22Q716.76,267.11 720.74,248.84Q724.72,230.57 737.39,219.37Q750.07,208.17 767.48,207.41Q784.89,206.65 797.09,222.33Q839.85,275.04 863.99,340.78Q888.13,406.52 888.13,480Q888.13,564.91 856.03,639.35Q823.93,713.78 768.86,768.86Q713.78,823.93 639.35,856.03Q564.91,888.13 480,888.13ZM480,525.5Q460.85,525.5 447.67,512.33Q434.5,499.15 434.5,480L434.5,117.37Q434.5,98.22 447.67,85.04Q460.85,71.87 480,71.87Q499.15,71.87 512.33,85.04Q525.5,98.22 525.5,117.37L525.5,480Q525.5,499.15 512.33,512.33Q499.15,525.5 480,525.5Z"/> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable-watch/ic_restart.xml b/core/res/res/drawable-watch/ic_restart.xml index 52933aae8fe0..ddcfd259d7b3 100644 --- a/core/res/res/drawable-watch/ic_restart.xml +++ b/core/res/res/drawable-watch/ic_restart.xml @@ -14,13 +14,6 @@ ~ limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24" - android:tint="@android:color/white"> - <path - android:fillColor="@android:color/white" - android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02c-2.83,-0.48 -5,-2.94 -5,-5.91zM20,13c0,-4.42 -3.58,-8 -8,-8 -0.06,0 -0.12,0.01 -0.18,0.01l1.09,-1.09L11.5,2.5 8,6l3.5,3.5 1.41,-1.41 -1.08,-1.08c0.06,0 0.12,-0.01 0.17,-0.01 3.31,0 6,2.69 6,6 0,2.97 -2.17,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93z"/> -</vector> +<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="M385.83,834.46Q282.35,803.54 217.11,717.61Q151.87,631.67 151.87,520.48Q151.87,464.91 169.91,414.25Q187.96,363.59 220.8,320.83Q232.76,305.72 252.11,304.98Q271.46,304.24 286.85,319.63Q298.57,331.35 299.3,348.66Q300.04,365.98 288.8,381.41Q266.72,411.22 254.79,446.54Q242.87,481.87 242.87,520.48Q242.87,599.33 288.46,661.27Q334.04,723.22 406.41,746.7Q421.09,751.65 430.54,764.09Q440,776.52 440,790.96Q440,814.3 423.73,827.6Q407.46,840.89 385.83,834.46ZM574.17,834.46Q552.54,840.89 536.27,827.22Q520,813.54 520,790.2Q520,776.76 529.46,764.21Q538.91,751.65 553.59,746.7Q625.72,722.22 671.42,660.65Q717.13,599.09 717.13,520.48Q717.13,423.11 649.64,354.3Q582.15,285.5 485.02,283.59L481.07,283.59L497.3,299.83Q509.02,311.54 509.02,329.14Q509.02,346.74 497.3,358.46Q485.59,370.17 467.99,370.17Q450.39,370.17 438.67,358.46L348.46,268.24Q341.74,261.52 338.76,253.45Q335.78,245.37 335.78,236.41Q335.78,227.46 338.76,219.38Q341.74,211.3 348.46,204.59L438.67,114.13Q450.39,102.41 467.99,102.41Q485.59,102.41 497.3,114.13Q509.02,125.85 509.02,143.45Q509.02,161.04 497.3,172.76L477.72,192.35L481.91,192.35Q618.54,192.35 713.34,287.98Q808.13,383.61 808.13,520.48Q808.13,630.91 742.89,717.11Q677.65,803.3 574.17,834.46Z"/> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable-watch/ic_settings.xml b/core/res/res/drawable-watch/ic_settings.xml new file mode 100644 index 000000000000..cef10e9eed71 --- /dev/null +++ b/core/res/res/drawable-watch/ic_settings.xml @@ -0,0 +1,19 @@ +<!-- + ~ 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. + --> + +<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="M428.46,888.13Q400.26,888.13 379.92,869.53Q359.59,850.93 355.59,823.74L346.59,757.5Q335.5,753.22 325.55,747.17Q315.61,741.13 306.04,734.33L244.28,760.33Q218.33,771.57 192.13,762.45Q165.93,753.33 151.46,729.13L99.91,638.76Q85.43,614.8 91.55,587.73Q97.67,560.65 119.63,543.17L172.39,503.17Q171.63,497.13 171.63,491.59Q171.63,486.04 171.63,480Q171.63,473.96 171.63,468.41Q171.63,462.87 172.39,456.83L119.63,417.07Q97.43,399.59 91.43,372.51Q85.43,345.43 99.91,321.24L151.46,231.11Q165.93,207.15 192.01,197.91Q218.09,188.67 244.04,199.91L306.52,225.91Q316.09,219.11 326.17,213.18Q336.26,207.26 346.59,202.98L355.59,136.5Q359.59,109.07 379.92,90.47Q400.26,71.87 428.46,71.87L531.54,71.87Q559.74,71.87 580.08,90.47Q600.41,109.07 604.41,136.5L613.41,202.98Q624.5,207.26 634.45,213.18Q644.39,219.11 653.96,225.91L715.72,199.91Q741.67,188.67 767.87,197.91Q794.07,207.15 808.54,231.11L860.09,321.24Q874.57,345.43 868.57,372.51Q862.57,399.59 840.37,417.07L787.37,456.83Q788.13,462.87 788.13,468.41Q788.13,473.96 788.13,480Q788.13,486.04 788.01,491.59Q787.89,497.13 786.37,503.17L839.37,542.93Q861.57,560.41 867.57,587.49Q873.57,614.57 859.09,638.76L806.54,729.13Q792.07,753.09 765.99,762.33Q739.91,771.57 713.96,760.33L653.48,734.33Q643.91,741.13 633.83,747.17Q623.74,753.22 613.41,757.5L604.41,823.74Q600.41,850.93 580.08,869.53Q559.74,888.13 531.54,888.13L428.46,888.13ZM481.28,620Q539.28,620 580.28,579Q621.28,538 621.28,480Q621.28,422 580.28,381Q539.28,340 481.28,340Q422.52,340 381.9,381Q341.28,422 341.28,480Q341.28,538 381.9,579Q422.52,620 481.28,620Z"/> +</vector>
\ No newline at end of file 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/config_telephony.xml b/core/res/res/values/config_telephony.xml index 779422a70cad..31e9913dd988 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -472,10 +472,13 @@ <integer name="config_mt_sms_polling_throttle_millis">300000</integer> <java-symbol type="integer" name="config_mt_sms_polling_throttle_millis" /> - <!-- The receiver class of the intent that hidden menu sends to start satellite non-emergency mode --> <string name="config_satellite_carrier_roaming_non_emergency_session_class" translatable="false"></string> <java-symbol type="string" name="config_satellite_carrier_roaming_non_emergency_session_class" /> + <!-- Whether to show the system notification to users whenever there is a change + in the satellite availability state at the current location. --> + <bool name="config_satellite_should_notify_availability">false</bool> + <java-symbol type="bool" name="config_satellite_should_notify_availability" /> </resources> 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 d3ef07ca8122..e23e665e7335 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6522,6 +6522,54 @@ ul.</string> <string name="satellite_manual_selection_state_popup_cancel">Go back</string> <!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. --> <string name="unarchival_session_app_label">Pending...</string> + <!-- Notification title when satellite service is available. --> + <string name="satellite_sos_available_notification_title">Satellite SOS is now available</string> + <!-- Notification summary when satellite service is available. [CHAR LIMIT=NONE] --> + <string name="satellite_sos_available_notification_summary">You can message emergency services if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app.</string> + <!-- Notification title when satellite service is not supported by device. --> + <string name="satellite_sos_not_supported_notification_title">Satellite SOS isn\'t supported</string> + <!-- Notification summary when satellite service is not supported by device. [CHAR LIMIT=NONE] --> + <string name="satellite_sos_not_supported_notification_summary">Satellite SOS isn\'t supported on this device</string> + <!-- Notification title when satellite service is not provisioned. [CHAR LIMIT=NONE] --> + <string name="satellite_sos_not_provisioned_notification_title">Satellite SOS isn\'t set up</string> + <!-- Notification summary when satellite service is not provisioned. [CHAR LIMIT=NONE] --> + <string name="satellite_sos_not_provisioned_notification_summary">Make sure you\'re connected to the internet and try setup again</string> + <!-- Notification title when satellite service is not allowed at current location. --> + <string name="satellite_sos_not_in_allowed_region_notification_title">Satellite SOS isn\'t available</string> + <!-- Notification summary when satellite service is not allowed at current location. [CHAR LIMIT=NONE] --> + <string name="satellite_sos_not_in_allowed_region_notification_summary">Satellite SOS isn\'t available in this country or region</string> + <!-- Notification title when default messaging app does not support satellite. [CHAR LIMIT=NONE] --> + <string name="satellite_sos_unsupported_default_sms_app_notification_title">Satellite SOS not set up</string> + <!-- Notification summary when default messaging app does not support satellite. [CHAR LIMIT=NONE] --> + <string name="satellite_sos_unsupported_default_sms_app_notification_summary">To message by satellite, set Google Messages as your default messaging app</string> + <!-- Notification title when location settings is disabled. --> + <string name="satellite_sos_location_disabled_notification_title">Satellite SOS isn\'t available</string> + <!-- Notification summary when location settings is disabled. [CHAR LIMIT=NONE] --> + <string name="satellite_sos_location_disabled_notification_summary">To check if satellite SOS is available in this country or region, turn on location settings</string> + <!-- Notification title when satellite service is available. --> + <string name="satellite_messaging_available_notification_title">Satellite messaging available</string> + <!-- Notification summary when satellite service is available. [CHAR LIMIT=NONE] --> + <string name="satellite_messaging_available_notification_summary">You can message by satellite if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app.</string> + <!-- Notification title when satellite service is not supported by device. --> + <string name="satellite_messaging_not_supported_notification_title">Satellite messaging not supported</string> + <!-- Notification summary when satellite service is not supported by device. [CHAR LIMIT=NONE] --> + <string name="satellite_messaging_not_supported_notification_summary">Satellite messaging isn\'t supported on this device</string> + <!-- Notification title when satellite service is not provisioned. [CHAR LIMIT=NONE] --> + <string name="satellite_messaging_not_provisioned_notification_title">Satellite messaging not set up</string> + <!-- Notification summary when satellite service is not provisioned. [CHAR LIMIT=NONE] --> + <string name="satellite_messaging_not_provisioned_notification_summary">Make sure you\'re connected to the internet and try setup again</string> + <!-- Notification title when satellite service is not allowed at current location. --> + <string name="satellite_messaging_not_in_allowed_region_notification_title">Satellite messaging not available</string> + <!-- Notification summary when satellite service is not allowed at current location. [CHAR LIMIT=NONE] --> + <string name="satellite_messaging_not_in_allowed_region_notification_summary">Satellite messaging isn\'t available in this country or region</string> + <!-- Notification title when default messaging app does not support satellite. [CHAR LIMIT=NONE] --> + <string name="satellite_messaging_unsupported_default_sms_app_notification_title">Satellite messaging not set up</string> + <!-- Notification summary when default messaging app does not support satellite. [CHAR LIMIT=NONE] --> + <string name="satellite_messaging_unsupported_default_sms_app_notification_summary">To message by satellite, set Google Messages as your default messaging app</string> + <!-- Notification title when location settings is disabled. --> + <string name="satellite_messaging_location_disabled_notification_title">Satellite messaging not available</string> + <!-- Notification summary when location settings is disabled. [CHAR LIMIT=NONE] --> + <string name="satellite_messaging_location_disabled_notification_summary">To check if satellite messaging is available in this country or region, turn on location settings</string> <!-- Fingerprint dangling notification title --> <string name="fingerprint_dangling_notification_title">Set up Fingerprint Unlock again</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b7cb1981b2f2..fec8bbbfeb83 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5552,6 +5552,30 @@ <java-symbol type="string" name="satellite_manual_selection_state_popup_cancel" /> <java-symbol type="drawable" name="ic_satellite_alt_24px" /> <java-symbol type="drawable" name="ic_android_satellite_24px" /> + <java-symbol type="string" name="satellite_sos_available_notification_title" /> + <java-symbol type="string" name="satellite_sos_available_notification_summary" /> + <java-symbol type="string" name="satellite_sos_not_in_allowed_region_notification_title" /> + <java-symbol type="string" name="satellite_sos_not_in_allowed_region_notification_summary" /> + <java-symbol type="string" name="satellite_sos_not_supported_notification_title" /> + <java-symbol type="string" name="satellite_sos_not_supported_notification_summary" /> + <java-symbol type="string" name="satellite_sos_not_provisioned_notification_title" /> + <java-symbol type="string" name="satellite_sos_not_provisioned_notification_summary" /> + <java-symbol type="string" name="satellite_sos_unsupported_default_sms_app_notification_title" /> + <java-symbol type="string" name="satellite_sos_unsupported_default_sms_app_notification_summary" /> + <java-symbol type="string" name="satellite_sos_location_disabled_notification_title" /> + <java-symbol type="string" name="satellite_sos_location_disabled_notification_summary" /> + <java-symbol type="string" name="satellite_messaging_available_notification_title" /> + <java-symbol type="string" name="satellite_messaging_available_notification_summary" /> + <java-symbol type="string" name="satellite_messaging_not_in_allowed_region_notification_title" /> + <java-symbol type="string" name="satellite_messaging_not_in_allowed_region_notification_summary" /> + <java-symbol type="string" name="satellite_messaging_not_supported_notification_title" /> + <java-symbol type="string" name="satellite_messaging_not_supported_notification_summary" /> + <java-symbol type="string" name="satellite_messaging_not_provisioned_notification_title" /> + <java-symbol type="string" name="satellite_messaging_not_provisioned_notification_summary" /> + <java-symbol type="string" name="satellite_messaging_unsupported_default_sms_app_notification_title" /> + <java-symbol type="string" name="satellite_messaging_unsupported_default_sms_app_notification_summary" /> + <java-symbol type="string" name="satellite_messaging_location_disabled_notification_title" /> + <java-symbol type="string" name="satellite_messaging_location_disabled_notification_summary" /> <!-- DisplayManager configs. --> <java-symbol type="bool" name="config_evenDimmerEnabled" /> 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/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java index 120a4de54427..3239598eccdc 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java @@ -301,18 +301,14 @@ public class KernelSingleUidTimeReaderTest { {1_000_000, 2_000_000, 3_000_000}, {4_000_000, 5_000_000}}); - LongArrayMultiStateCounter.LongArrayContainer array = - new LongArrayMultiStateCounter.LongArrayContainer(5); + long[] out = new long[5]; - success = mInjector.addDelta(TEST_UID, counter, 2000, array); + success = mInjector.addDelta(TEST_UID, counter, 2000, out); assertThat(success).isTrue(); - - array.getValues(out); assertThat(out).isEqualTo(new long[]{1, 2, 3, 4, 5}); - counter.getCounts(array, 0); - array.getValues(out); + counter.getCounts(out, 0); assertThat(out).isEqualTo(new long[]{1, 2, 3, 4, 5}); counter.setState(1, 3000); @@ -322,18 +318,14 @@ public class KernelSingleUidTimeReaderTest { {11_000_000, 22_000_000, 33_000_000}, {44_000_000, 55_000_000}}); - success = mInjector.addDelta(TEST_UID, counter, 4000, array); + success = mInjector.addDelta(TEST_UID, counter, 4000, out); assertThat(success).isTrue(); - - array.getValues(out); assertThat(out).isEqualTo(new long[]{10, 20, 30, 40, 50}); - counter.getCounts(array, 0); - array.getValues(out); + counter.getCounts(out, 0); assertThat(out).isEqualTo(new long[]{1 + 5, 2 + 10, 3 + 15, 4 + 20, 5 + 25}); - counter.getCounts(array, 1); - array.getValues(out); + counter.getCounts(out, 1); assertThat(out).isEqualTo(new long[]{5, 10, 15, 20, 25}); } @@ -385,7 +377,7 @@ public class KernelSingleUidTimeReaderTest { @Override public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs, - LongArrayMultiStateCounter.LongArrayContainer deltaOut) { + long[] deltaOut) { return addDeltaForTest(uid, counter, timestampMs, mCpuTimeInStatePerClusterNs, deltaOut); } diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java index b86dc5807b22..7e5d0a4c2e42 100644 --- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java +++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java @@ -24,14 +24,11 @@ import android.os.BadParcelableException; import android.os.Parcel; import android.platform.test.ravenwood.RavenwoodRule; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Rule; import org.junit.Test; -import org.junit.runner.RunWith; -@RunWith(AndroidJUnit4.class) @SmallTest public class LongArrayMultiStateCounterTest { @Rule @@ -41,11 +38,11 @@ public class LongArrayMultiStateCounterTest { public void setStateAndUpdateValue() { LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4); - updateValue(counter, new long[]{0, 0, 0, 0}, 1000); + counter.updateValues(new long[]{0, 0, 0, 0}, 1000); counter.setState(0, 1000); counter.setState(1, 2000); counter.setState(0, 4000); - updateValue(counter, new long[]{100, 200, 300, 400}, 9000); + counter.updateValues(new long[]{100, 200, 300, 400}, 9000); assertCounts(counter, 0, new long[]{75, 150, 225, 300}); assertCounts(counter, 1, new long[]{25, 50, 75, 100}); @@ -55,15 +52,28 @@ public class LongArrayMultiStateCounterTest { } @Test + public void increment() { + LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4); + + counter.updateValues(new long[]{0, 0, 0, 0}, 1000); + counter.setState(0, 1000); + counter.incrementValues(new long[]{1, 2, 3, 4}, 2000); + counter.incrementValues(new long[]{100, 200, 300, 400}, 3000); + + assertCounts(counter, 0, new long[]{101, 202, 303, 404}); + assertCounts(counter, 1, new long[]{0, 0, 0, 0}); + } + + @Test public void copyStatesFrom() { LongArrayMultiStateCounter source = new LongArrayMultiStateCounter(2, 1); - updateValue(source, new long[]{0}, 1000); + source.updateValues(new long[]{0}, 1000); source.setState(0, 1000); source.setState(1, 2000); LongArrayMultiStateCounter target = new LongArrayMultiStateCounter(2, 1); target.copyStatesFrom(source); - updateValue(target, new long[]{1000}, 5000); + target.updateValues(new long[]{1000}, 5000); assertCounts(target, 0, new long[]{250}); assertCounts(target, 1, new long[]{750}); @@ -83,25 +93,25 @@ public class LongArrayMultiStateCounterTest { public void setEnabled() { LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4); counter.setState(0, 1000); - updateValue(counter, new long[]{0, 0, 0, 0}, 1000); - updateValue(counter, new long[]{100, 200, 300, 400}, 2000); + counter.updateValues(new long[]{0, 0, 0, 0}, 1000); + counter.updateValues(new long[]{100, 200, 300, 400}, 2000); assertCounts(counter, 0, new long[]{100, 200, 300, 400}); counter.setEnabled(false, 3000); // Partially included, because the counter is disabled after the previous update - updateValue(counter, new long[]{200, 300, 400, 500}, 4000); + counter.updateValues(new long[]{200, 300, 400, 500}, 4000); // Count only 50%, because the counter was disabled for 50% of the time assertCounts(counter, 0, new long[]{150, 250, 350, 450}); // Not counted because the counter is disabled - updateValue(counter, new long[]{250, 350, 450, 550}, 5000); + counter.updateValues(new long[]{250, 350, 450, 550}, 5000); counter.setEnabled(true, 6000); - updateValue(counter, new long[]{300, 400, 500, 600}, 7000); + counter.updateValues(new long[]{300, 400, 500, 600}, 7000); // Again, take 50% of the delta assertCounts(counter, 0, new long[]{175, 275, 375, 475}); @@ -111,8 +121,8 @@ public class LongArrayMultiStateCounterTest { public void reset() { LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4); counter.setState(0, 1000); - updateValue(counter, new long[]{0, 0, 0, 0}, 1000); - updateValue(counter, new long[]{100, 200, 300, 400}, 2000); + counter.updateValues(new long[]{0, 0, 0, 0}, 1000); + counter.updateValues(new long[]{100, 200, 300, 400}, 2000); assertCounts(counter, 0, new long[]{100, 200, 300, 400}); @@ -120,8 +130,8 @@ public class LongArrayMultiStateCounterTest { assertCounts(counter, 0, new long[]{0, 0, 0, 0}); - updateValue(counter, new long[]{200, 300, 400, 500}, 3000); - updateValue(counter, new long[]{300, 400, 500, 600}, 4000); + counter.updateValues(new long[]{200, 300, 400, 500}, 3000); + counter.updateValues(new long[]{300, 400, 500, 600}, 4000); assertCounts(counter, 0, new long[]{100, 100, 100, 100}); } @@ -129,11 +139,11 @@ public class LongArrayMultiStateCounterTest { @Test public void parceling() { LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4); - updateValue(counter, new long[]{0, 0, 0, 0}, 1000); + counter.updateValues(new long[]{0, 0, 0, 0}, 1000); counter.setState(0, 1000); - updateValue(counter, new long[]{100, 200, 300, 400}, 2000); + counter.updateValues(new long[]{100, 200, 300, 400}, 2000); counter.setState(1, 2000); - updateValue(counter, new long[]{101, 202, 304, 408}, 3000); + counter.updateValues(new long[]{101, 202, 304, 408}, 3000); assertCounts(counter, 0, new long[]{100, 200, 300, 400}); assertCounts(counter, 1, new long[]{1, 2, 4, 8}); @@ -158,27 +168,17 @@ public class LongArrayMultiStateCounterTest { // State, last update timestamp and current counts are undefined at this point. newCounter.setState(0, 100); - updateValue(newCounter, new long[]{300, 400, 500, 600}, 100); + newCounter.updateValues(new long[]{300, 400, 500, 600}, 100); // A new base state and counters are established; we can continue accumulating deltas - updateValue(newCounter, new long[]{316, 432, 564, 728}, 200); + newCounter.updateValues(new long[]{316, 432, 564, 728}, 200); assertCounts(newCounter, 0, new long[]{116, 232, 364, 528}); } - private void updateValue(LongArrayMultiStateCounter counter, long[] values, int timestamp) { - LongArrayMultiStateCounter.LongArrayContainer container = - new LongArrayMultiStateCounter.LongArrayContainer(values.length); - container.setValues(values); - counter.updateValues(container, timestamp); - } - private void assertCounts(LongArrayMultiStateCounter counter, int state, long[] expected) { - LongArrayMultiStateCounter.LongArrayContainer container = - new LongArrayMultiStateCounter.LongArrayContainer(expected.length); long[] counts = new long[expected.length]; - counter.getCounts(container, state); - container.getValues(counts); + counter.getCounts(counts, state); assertThat(counts).isEqualTo(expected); } @@ -230,33 +230,4 @@ public class LongArrayMultiStateCounterTest { parcel.writeInt(endPos - startPos); parcel.setDataPosition(endPos); } - - @Test - public void combineValues() { - long[] values = new long[] {0, 1, 2, 3, 42}; - LongArrayMultiStateCounter.LongArrayContainer container = - new LongArrayMultiStateCounter.LongArrayContainer(values.length); - container.setValues(values); - - long[] out = new long[3]; - int[] indexes = {2, 1, 1, 0, 0}; - boolean nonZero = container.combineValues(out, indexes); - assertThat(nonZero).isTrue(); - assertThat(out).isEqualTo(new long[]{45, 3, 0}); - - // All zeros - container.setValues(new long[]{0, 0, 0, 0, 0}); - nonZero = container.combineValues(out, indexes); - assertThat(nonZero).isFalse(); - assertThat(out).isEqualTo(new long[]{0, 0, 0}); - - // Index out of range - IndexOutOfBoundsException e1 = assertThrows( - IndexOutOfBoundsException.class, - () -> container.combineValues(out, new int[]{0, 1, -1, 0, 0})); - assertThat(e1.getMessage()).isEqualTo("Index -1 is out of bounds: [0, 2]"); - IndexOutOfBoundsException e2 = assertThrows(IndexOutOfBoundsException.class, - () -> container.combineValues(out, new int[]{0, 1, 4, 0, 0})); - assertThat(e2.getMessage()).isEqualTo("Index 4 is out of bounds: [0, 2]"); - } } 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/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/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java index 88878c6adcf2..e033f673d07d 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java @@ -360,7 +360,7 @@ public class TransitionUtil { windowConfiguration = new WindowConfiguration(); } - Rect localBounds = new Rect(); + Rect bounds = windowConfiguration.getBounds(); RemoteAnimationTarget target = new RemoteAnimationTarget( taskId, newModeToLegacyMode(mode), @@ -373,12 +373,12 @@ public class TransitionUtil { new Rect(0, 0, 0, 0), order, null, - localBounds, - new Rect(), + bounds, + bounds, windowConfiguration, isNotInRecents, null, - new Rect(), + bounds, taskInfo, false, INVALID_WINDOW_TYPE 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/shared/src/com/android/wm/shell/shared/pip/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS new file mode 100644 index 000000000000..20d5c33dc8bf --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS @@ -0,0 +1,4 @@ +# WM shell sub-module PiP owner +hwwang@google.com +gabiyev@google.com +wuperry@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index f59fed906e2d..dfe76b8543e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -487,6 +487,20 @@ public class ShellTaskOrganizer extends TaskOrganizer { return mHomeTaskOverlayContainer; } + /** + * Returns the home task surface, not for wide use. + */ + @Nullable + public SurfaceControl getHomeTaskSurface() { + for (int i = 0; i < mTasks.size(); i++) { + final TaskAppearedInfo info = mTasks.valueAt(i); + if (info.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) { + return info.getLeash(); + } + } + return null; + } + @Override public void addStartingWindow(StartingWindowInfo info) { if (mStartingWindow != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 5f0eed9daa1a..14f8cc74bfc5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1632,6 +1632,7 @@ public class BubbleController implements ConfigurationChangeListener, if (!isShowingAsBubbleBar()) { callback = b -> { if (mStackView != null) { + b.setSuppressFlyout(true); mStackView.addBubble(b); mStackView.setSelectedBubble(b); } else { 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/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt index d1b2347a4411..62d5098f2a27 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt @@ -23,9 +23,15 @@ import android.content.Context import com.android.internal.R // TODO(b/347289970): Consider replacing with API +/** + * If the top activity should be exempt from desktop windowing and forced back to fullscreen. + * Currently includes all system ui activities and modal dialogs. However is the top activity is not + * being displayed, regardless of its configuration, we will not exempt it as to remain in the + * desktop windowing environment. + */ fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) = - isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1 - && !task.isTopActivityStyleFloating) + (isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1)) + && !task.isTopActivityNoDisplay private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean { val sysUiPackageName: String = 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/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 2a5a519272c7..77e041ee7cdb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -401,9 +401,6 @@ public abstract class WMShellBaseModule { ShellInit shellInit, ShellCommandHandler shellCommandHandler, RootTaskDisplayAreaOrganizer rootTdaOrganizer) { - if (!com.android.window.flags.Flags.explicitRefreshRateHints()) { - return Optional.empty(); - } final PerfHintController perfHintController = new PerfHintController(context, shellInit, shellCommandHandler, rootTdaOrganizer); return Optional.of(perfHintController.getHinter()); 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 fec4c16992a5..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 @@ -67,6 +67,7 @@ import com.android.wm.shell.dagger.pip.PipModule; import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler; +import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler; import com.android.wm.shell.desktopmode.DesktopImmersiveController; import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler; import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler; @@ -778,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, @@ -788,7 +790,8 @@ public abstract class WMShellModule { shellTaskOrganizer, toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, - desktopRepository + desktopRepository, + desktopModeEventLogger ); } @@ -1016,6 +1019,30 @@ public abstract class WMShellModule { @WMSingleton @Provides + static Optional<DesktopDisplayEventHandler> provideDesktopDisplayEventHandler( + Context context, + ShellInit shellInit, + Transitions transitions, + DisplayController displayController, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + IWindowManager windowManager + ) { + if (!DesktopModeStatus.canEnterDesktopMode(context) + || !Flags.enableDisplayWindowingModeSwitching()) { + return Optional.empty(); + } + return Optional.of( + new DesktopDisplayEventHandler( + context, + shellInit, + transitions, + displayController, + rootTaskDisplayAreaOrganizer, + windowManager)); + } + + @WMSingleton + @Provides static AppHandleEducationDatastoreRepository provideAppHandleEducationDatastoreRepository( Context context) { return new AppHandleEducationDatastoreRepository(context); @@ -1180,7 +1207,8 @@ public abstract class WMShellModule { @Provides static Object provideIndependentShellComponentsToCreate( DragAndDropController dragAndDropController, - Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional) { + Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional, + Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler) { return new Object(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt new file mode 100644 index 000000000000..ba383fac8b47 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.android.wm.shell.desktopmode + +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.content.Context +import android.provider.Settings +import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS +import android.view.Display.DEFAULT_DISPLAY +import android.view.IWindowManager +import android.view.WindowManager.TRANSIT_CHANGE +import android.window.WindowContainerTransaction +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.transition.Transitions + +/** Handles display events in desktop mode */ +class DesktopDisplayEventHandler( + private val context: Context, + shellInit: ShellInit, + private val transitions: Transitions, + private val displayController: DisplayController, + private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + private val windowManager: IWindowManager, +) : OnDisplaysChangedListener { + + init { + shellInit.addInitCallback({ onInit() }, this) + } + + private fun onInit() { + displayController.addDisplayWindowListener(this) + } + + override fun onDisplayAdded(displayId: Int) { + if (displayId == DEFAULT_DISPLAY) { + return + } + refreshDisplayWindowingMode() + } + + override fun onDisplayRemoved(displayId: Int) { + if (displayId == DEFAULT_DISPLAY) { + return + } + refreshDisplayWindowingMode() + } + + private fun refreshDisplayWindowingMode() { + // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available. + val isExtendedDisplayEnabled = 0 != Settings.Global.getInt( + context.contentResolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0 + ) + if (!isExtendedDisplayEnabled) { + // No action needed in mirror or projected mode. + return + } + + val hasNonDefaultDisplay = rootTaskDisplayAreaOrganizer.getDisplayIds() + .any { displayId -> displayId != DEFAULT_DISPLAY } + val targetDisplayWindowingMode = + if (hasNonDefaultDisplay) { + WINDOWING_MODE_FREEFORM + } else { + // Use the default display windowing mode when no non-default display. + windowManager.getWindowingMode(DEFAULT_DISPLAY) + } + val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY) + requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." } + if (tdaInfo.configuration.windowConfiguration.windowingMode == targetDisplayWindowingMode) { + // Already in the target mode. + return + } + + val wct = WindowContainerTransaction() + wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode) + transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) + } +} 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/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 75f8839692a2..162879c97a16 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -554,19 +554,6 @@ class DesktopTasksController( } } - /** Move a desktop app to split screen. */ - fun moveToSplit(task: RunningTaskInfo) { - logV( "moveToSplit taskId=%s", task.taskId) - desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId) - val wct = WindowContainerTransaction() - wct.setBounds(task.token, Rect()) - // Rather than set windowing mode to multi-window at task level, set it to - // undefined and inherit from split stage. - wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED) - - transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) - } - private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) { if (splitScreenController.isTaskInSplitScreen(taskInfo.taskId)) { splitScreenController.prepareExitSplitScreen( @@ -2050,6 +2037,18 @@ class DesktopTasksController( } /** + * Cancel the drag-to-desktop transition. + * + * @param taskInfo the task being dragged. + */ + fun onDragPositioningCancelThroughStatusBar( + taskInfo: RunningTaskInfo, + ) { + interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) + cancelDragToDesktop(taskInfo) + } + + /** * Perform checks required when drag ends under status bar area. * * @param taskInfo the task being dragged. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt index 96719fa2301a..941115083717 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt @@ -21,6 +21,7 @@ import android.animation.RectEvaluator import android.animation.ValueAnimator import android.graphics.Rect import android.os.IBinder +import android.view.Choreographer import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.window.TransitionInfo @@ -126,6 +127,7 @@ class ToggleResizeDesktopTaskTransitionHandler( tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat()) .setWindowCrop(leash, rect.width(), rect.height()) .show(leash) + .setFrameTimeline(Choreographer.getInstance().getVsyncId()) onTaskResizeAnimationListener.onBoundsChange(taskId, tx, rect) } start() 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..3ad9950fdd52 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 @@ -26,7 +26,6 @@ import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING; -import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING; import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -194,7 +193,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 +201,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 +324,28 @@ public class KeyguardTransitionHandler return false; } + private static boolean isKeyguardOccluding(@NonNull TransitionInfo info) { + 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) { + 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/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java index e5137582822d..eb33ff4c1c8e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java @@ -20,6 +20,7 @@ import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.content.Context; @@ -34,6 +35,7 @@ import android.window.TransitionInfo; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.common.pip.PipUtils; @@ -45,8 +47,7 @@ import com.android.wm.shell.shared.pip.PipContentOverlay; /** * Animator that handles bounds animations for entering PIP. */ -public class PipEnterAnimator extends ValueAnimator - implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener { +public class PipEnterAnimator extends ValueAnimator { @NonNull private final SurfaceControl mLeash; private final SurfaceControl.Transaction mStartTransaction; private final SurfaceControl.Transaction mFinishTransaction; @@ -56,49 +57,82 @@ public class PipEnterAnimator extends ValueAnimator private final RectEvaluator mRectEvaluator; private final Rect mEndBounds = new Rect(); - @Nullable private final Rect mSourceRectHint; private final @Surface.Rotation int mRotation; @Nullable private Runnable mAnimationStartCallback; @Nullable private Runnable mAnimationEndCallback; - private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; Matrix mTransformTensor = new Matrix(); final float[] mMatrixTmp = new float[9]; @Nullable private PipContentOverlay mContentOverlay; + private PipAppIconOverlaySupplier mPipAppIconOverlaySupplier; // Internal state representing initial transform - cached to avoid recalculation. private final PointF mInitScale = new PointF(); private final PointF mInitPos = new PointF(); private final Rect mInitCrop = new Rect(); - private final PointF mInitActivityScale = new PointF(); - private final PointF mInitActivityPos = new PointF(); + + private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + if (mAnimationStartCallback != null) { + mAnimationStartCallback.run(); + } + if (mStartTransaction != null) { + onEnterAnimationUpdate(0f /* fraction */, mStartTransaction); + mStartTransaction.apply(); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (mFinishTransaction != null) { + onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction); + } + if (mAnimationEndCallback != null) { + mAnimationEndCallback.run(); + } + } + }; + + private final AnimatorUpdateListener mAnimatorUpdateListener = new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(@NonNull ValueAnimator animation) { + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); + final float fraction = getAnimatedFraction(); + onEnterAnimationUpdate(fraction, tx); + tx.apply(); + } + }; public PipEnterAnimator(Context context, @NonNull SurfaceControl leash, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, @NonNull Rect endBounds, - @Nullable Rect sourceRectHint, @Surface.Rotation int rotation) { mLeash = leash; mStartTransaction = startTransaction; mFinishTransaction = finishTransaction; mRectEvaluator = new RectEvaluator(mAnimatedRect); mEndBounds.set(endBounds); - mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null; mRotation = rotation; mSurfaceControlTransactionFactory = new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); + mPipAppIconOverlaySupplier = this::getAppIconOverlay; final int enterAnimationDuration = context.getResources() .getInteger(R.integer.config_pipEnterAnimationDuration); setDuration(enterAnimationDuration); setFloatValues(0f, 1f); setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - addListener(this); - addUpdateListener(this); + addListener(mAnimatorListener); + addUpdateListener(mAnimatorUpdateListener); } public void setAnimationStartCallback(@NonNull Runnable runnable) { @@ -109,35 +143,6 @@ public class PipEnterAnimator extends ValueAnimator mAnimationEndCallback = runnable; } - @Override - public void onAnimationStart(@NonNull Animator animation) { - if (mAnimationStartCallback != null) { - mAnimationStartCallback.run(); - } - if (mStartTransaction != null) { - onEnterAnimationUpdate(0f /* fraction */, mStartTransaction); - mStartTransaction.apply(); - } - } - - @Override - public void onAnimationEnd(@NonNull Animator animation) { - if (mFinishTransaction != null) { - onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction); - } - if (mAnimationEndCallback != null) { - mAnimationEndCallback.run(); - } - } - - @Override - public void onAnimationUpdate(@NonNull ValueAnimator animation) { - final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); - final float fraction = getAnimatedFraction(); - onEnterAnimationUpdate(fraction, tx); - tx.apply(); - } - /** * Updates the transaction to reflect the state of PiP leash at a certain fraction during enter. * @@ -177,14 +182,6 @@ public class PipEnterAnimator extends ValueAnimator } } - // no-ops - - @Override - public void onAnimationCancel(@NonNull Animator animation) {} - - @Override - public void onAnimationRepeat(@NonNull Animator animation) {} - /** * Caches the initial transform relevant values for the bounds enter animation. * @@ -201,18 +198,13 @@ public class PipEnterAnimator extends ValueAnimator */ public void setAppIconContentOverlay(Context context, Rect appBounds, Rect destinationBounds, ActivityInfo activityInfo, int appIconSizePx) { - reattachAppIconOverlay( - new PipAppIconOverlay(context, appBounds, destinationBounds, - new IconProvider(context).getIcon(activityInfo), appIconSizePx)); - } - - private void reattachAppIconOverlay(PipAppIconOverlay overlay) { final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); if (mContentOverlay != null) { mContentOverlay.detach(tx); } - mContentOverlay = overlay; + mContentOverlay = mPipAppIconOverlaySupplier.get(context, appBounds, destinationBounds, + activityInfo, appIconSizePx); mContentOverlay.attach(tx, mLeash); } @@ -229,6 +221,13 @@ public class PipEnterAnimator extends ValueAnimator mContentOverlay = null; } + private PipAppIconOverlay getAppIconOverlay( + Context context, Rect appBounds, Rect destinationBounds, + ActivityInfo activityInfo, int iconSize) { + return new PipAppIconOverlay(context, appBounds, destinationBounds, + new IconProvider(context).getIcon(activityInfo), iconSize); + } + /** * @return the app icon overlay leash; null if no overlay is attached. */ @@ -239,4 +238,21 @@ public class PipEnterAnimator extends ValueAnimator } return mContentOverlay.getLeash(); } + + @VisibleForTesting + void setSurfaceControlTransactionFactory( + @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) { + mSurfaceControlTransactionFactory = factory; + } + + @VisibleForTesting + interface PipAppIconOverlaySupplier { + PipAppIconOverlay get(Context context, Rect appBounds, Rect destinationBounds, + ActivityInfo activityInfo, int iconSize); + } + + @VisibleForTesting + void setPipAppIconOverlaySupplier(@NonNull PipAppIconOverlaySupplier supplier) { + mPipAppIconOverlaySupplier = supplier; + } } 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/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/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java index 2c7584af1f07..1b5eebaa8aa1 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,7 @@ import android.view.SurfaceControl; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +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 +37,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; @@ -121,6 +123,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())) { @@ -171,6 +176,7 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, "Leash is null for bounds transition."); if (mWaitingForAspectRatioChange) { + mWaitingForAspectRatioChange = false; PipResizeAnimator animator = new PipResizeAnimator(mContext, mPipTransitionState.mPinnedTaskLeash, startTx, finishTx, destinationBounds, 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..9af1aba51c57 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()) { 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 64d8887ae5cd..3930b423d8a1 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,20 +347,14 @@ public class PipTransition extends PipTransitionController implements : startRotation - endRotation; if (delta != ROTATION_0) { mPipTransitionState.setInFixedRotation(true); - handleBoundsTypeFixedRotation(pipChange, pipActivityChange, endRotation); - } - - Rect sourceRectHint = null; - if (pipChange.getTaskInfo() != null - && pipChange.getTaskInfo().pictureInPictureParams != null) { - sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint(); + handleBoundsEnterFixedRotation(pipChange, pipActivityChange, endRotation); } prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange, pipActivityChange); startTransaction.merge(finishTransaction); PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash, - startTransaction, finishTransaction, destinationBounds, sourceRectHint, delta); + startTransaction, finishTransaction, destinationBounds, delta); animator.setEnterStartState(pipChange); animator.onEnterAnimationUpdate(1.0f /* fraction */, startTransaction); startTransaction.apply(); @@ -420,20 +412,20 @@ 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()); } PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash, - startTransaction, finishTransaction, endBounds, adjustedSourceRectHint, delta); + startTransaction, finishTransaction, endBounds, delta); if (sourceRectHint == null) { // update the src-rect-hint in params in place, to set up initial animator transform. params.getSourceRectHint().set(adjustedSourceRectHint); @@ -465,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(); @@ -498,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, @@ -550,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; } @@ -723,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 // 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/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 6d4d4b410be8..40065b9287a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -544,10 +544,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, .findFirst() .get(); final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget( - homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_OPEN, + homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_OPEN, 0, true /* isTranslucent */); final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget( - homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_CLOSE, + homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_CLOSE, 0, true /* isTranslucent */); final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>(); apps.add(openingTarget); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index b33f3e98f350..4407e5b3106f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -245,9 +245,9 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { return; } mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); - mVisible = taskInfo.isVisible && taskInfo.isVisibleRequested; + mVisible = isStageVisible(); mCallbacks.onChildTaskStatusChanged(this, taskInfo.taskId, true /* present */, - mVisible); + taskInfo.isVisible && taskInfo.isVisibleRequested); } else { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); @@ -293,6 +293,19 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { t.reparent(sc, findTaskSurface(taskId)); } + /** + * Checks against all children task info and return true if any are marked as visible. + */ + private boolean isStageVisible() { + for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { + if (mChildrenTaskInfo.valueAt(i).isVisible + && mChildrenTaskInfo.valueAt(i).isVisibleRequested) { + return true; + } + } + return false; + } + private SurfaceControl findTaskSurface(int taskId) { if (mRootTaskInfo.taskId == taskId) { return mRootLeash; 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 e1683f3a903c..17e3dd2fc68e 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 @@ -41,7 +41,6 @@ import static com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; -import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import android.annotation.NonNull; import android.app.ActivityManager; @@ -49,6 +48,7 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.app.IActivityManager; import android.app.IActivityTaskManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.Point; @@ -122,8 +122,6 @@ import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition; -import com.android.wm.shell.splitscreen.SplitScreen; -import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -446,17 +444,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, @Override public void setSplitScreenController(SplitScreenController splitScreenController) { mSplitScreenController = splitScreenController; - mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() { - @Override - public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) { - if (visible && stage != STAGE_TYPE_UNDEFINED) { - DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId); - if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) { - mDesktopTasksController.moveToSplit(decor.mTaskInfo); - } - } - } - }); } @Override @@ -1290,6 +1277,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } break; } + case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: { if (mTransitionDragActive) { final DesktopModeVisualIndicator.DragStartState dragStartState = @@ -1304,32 +1292,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, // Though this isn't a hover event, we need to update handle's hover state // as it likely will change. relevantDecor.updateHoverAndPressStatus(ev); - DesktopModeVisualIndicator.IndicatorType resultType = - mDesktopTasksController.onDragPositioningEndThroughStatusBar( - new PointF(ev.getRawX(), ev.getRawY()), - relevantDecor.mTaskInfo, - relevantDecor.mTaskSurface); - // If we are entering split select, handle will no longer be visible and - // should not be receiving any input. - if (resultType == TO_SPLIT_LEFT_INDICATOR - || resultType == TO_SPLIT_RIGHT_INDICATOR) { - relevantDecor.disposeStatusBarInputLayer(); - // We should also dispose the other split task's input layer if - // applicable. - final int splitPosition = mSplitScreenController - .getSplitPosition(relevantDecor.mTaskInfo.taskId); - if (splitPosition != SPLIT_POSITION_UNDEFINED) { - final int oppositePosition = - splitPosition == SPLIT_POSITION_TOP_OR_LEFT - ? SPLIT_POSITION_BOTTOM_OR_RIGHT - : SPLIT_POSITION_TOP_OR_LEFT; - final RunningTaskInfo oppositeTaskInfo = - mSplitScreenController.getTaskInfo(oppositePosition); - if (oppositeTaskInfo != null) { - mWindowDecorByTaskId.get(oppositeTaskInfo.taskId) - .disposeStatusBarInputLayer(); - } - } + if (ev.getActionMasked() == ACTION_CANCEL) { + mDesktopTasksController.onDragPositioningCancelThroughStatusBar( + relevantDecor.mTaskInfo); + } else { + endDragToDesktop(ev, relevantDecor); } mMoveToDesktopAnimator = null; return; @@ -1378,10 +1345,35 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } break; } + } + } - case MotionEvent.ACTION_CANCEL: { - mTransitionDragActive = false; - mMoveToDesktopAnimator = null; + private void endDragToDesktop(MotionEvent ev, DesktopModeWindowDecoration relevantDecor) { + DesktopModeVisualIndicator.IndicatorType resultType = + mDesktopTasksController.onDragPositioningEndThroughStatusBar( + new PointF(ev.getRawX(), ev.getRawY()), + relevantDecor.mTaskInfo, + relevantDecor.mTaskSurface); + // If we are entering split select, handle will no longer be visible and + // should not be receiving any input. + if (resultType == TO_SPLIT_LEFT_INDICATOR + || resultType == TO_SPLIT_RIGHT_INDICATOR) { + relevantDecor.disposeStatusBarInputLayer(); + // We should also dispose the other split task's input layer if + // applicable. + final int splitPosition = mSplitScreenController + .getSplitPosition(relevantDecor.mTaskInfo.taskId); + if (splitPosition != SPLIT_POSITION_UNDEFINED) { + final int oppositePosition = + splitPosition == SPLIT_POSITION_TOP_OR_LEFT + ? SPLIT_POSITION_BOTTOM_OR_RIGHT + : SPLIT_POSITION_TOP_OR_LEFT; + final RunningTaskInfo oppositeTaskInfo = + mSplitScreenController.getTaskInfo(oppositePosition); + if (oppositeTaskInfo != null) { + mWindowDecorByTaskId.get(oppositeTaskInfo.taskId) + .disposeStatusBarInputLayer(); + } } } } @@ -1481,6 +1473,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, && isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) { return false; } + if (isPartOfDefaultHomePackage(taskInfo)) { + return false; + } return DesktopModeStatus.canEnterDesktopMode(mContext) && !DesktopWallpaperActivity.isWallpaperTask(taskInfo) && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED @@ -1488,6 +1483,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop(); } + private boolean isPartOfDefaultHomePackage(RunningTaskInfo taskInfo) { + final ComponentName currentDefaultHome = + mContext.getPackageManager().getHomeActivities(new ArrayList<>()); + return currentDefaultHome != null && taskInfo.baseActivity != null + && currentDefaultHome.getPackageName() + .equals(taskInfo.baseActivity.getPackageName()); + } + private void createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, 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/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt new file mode 100644 index 000000000000..e16159c0613a --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt @@ -0,0 +1,27 @@ +/* + * 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.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.MinimizeAppWindows +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [MinimizeAppWindows]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class MinimizeAppWindowsTest : MinimizeAppWindows() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt new file mode 100644 index 000000000000..b5483634b057 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.Rotation +import android.tools.flicker.rules.ChangeDisplayOrientationRule +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.NewTasksAppHelper +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +/** + * Base scenario test for minimizing all the desktop app windows one-by-one by clicking their + * minimize buttons. + */ +@Ignore("Test Base Class") +abstract class MinimizeAppWindows +constructor(private val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp1 = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + private val testApp2 = DesktopModeAppHelper(NonResizeableAppHelper(instrumentation)) + private val testApp3 = DesktopModeAppHelper(NewTasksAppHelper(instrumentation)) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + Assume.assumeTrue(Flags.enableMinimizeButton()) + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + ChangeDisplayOrientationRule.setRotation(rotation) + testApp1.enterDesktopWithDrag(wmHelper, device) + testApp2.launchViaIntent(wmHelper) + testApp3.launchViaIntent(wmHelper) + } + + @Test + open fun minimizeAllAppWindows() { + testApp3.minimizeDesktopApp(wmHelper, device) + testApp2.minimizeDesktopApp(wmHelper, device) + testApp1.minimizeDesktopApp(wmHelper, device) + } + + @After + fun teardown() { + testApp1.exit(wmHelper) + testApp2.exit(wmHelper) + testApp3.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java index 266e48482568..2ed7d07ac75e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java @@ -62,6 +62,7 @@ public class BackProgressAnimatorTest { @Before public void setUp() throws Exception { + mTargetProgressCalled = new CountDownLatch(1); mMainThreadHandler = new Handler(Looper.getMainLooper()); final BackMotionEvent backEvent = backMotionEventFrom(0, 0); mMainThreadHandler.post( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt index ecaf970ae389..803e5d4442a9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt @@ -43,38 +43,30 @@ class AppCompatUtilsTest : ShellTestCase() { .apply { isTopActivityTransparent = true numActivities = 1 - })) - assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, - createFreeformTask(/* displayId */ 0) - .apply { - isTopActivityTransparent = true - numActivities = 0 + isTopActivityNoDisplay = false })) } @Test - fun testIsTopActivityExemptFromDesktopWindowing_singleTopActivity() { - assertTrue(isTopActivityExemptFromDesktopWindowing(mContext, - createFreeformTask(/* displayId */ 0) - .apply { - isTopActivityTransparent = true - numActivities = 1 - })) + fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_multipleActivities() { assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, createFreeformTask(/* displayId */ 0) - .apply { - isTopActivityTransparent = false - numActivities = 1 - })) + .apply { + isTopActivityTransparent = true + numActivities = 2 + isTopActivityNoDisplay = false + })) } @Test - fun testIsTopActivityExemptFromDesktopWindowing__topActivityStyleFloating() { + fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_notDisplayed() { assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, createFreeformTask(/* displayId */ 0) - .apply { - isTopActivityStyleFloating = true - })) + .apply { + isTopActivityTransparent = true + numActivities = 1 + isTopActivityNoDisplay = true + })) } @Test @@ -85,6 +77,19 @@ class AppCompatUtilsTest : ShellTestCase() { createFreeformTask(/* displayId */ 0) .apply { baseActivity = baseComponent + isTopActivityNoDisplay = false })) } + + @Test + fun testIsTopActivityExemptFromDesktopWindowing_systemUiTask_notDisplayed() { + val systemUIPackageName = context.resources.getString(R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* class */ "") + assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, + createFreeformTask(/* displayId */ 0) + .apply { + baseActivity = baseComponent + isTopActivityNoDisplay = true + })) + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt new file mode 100644 index 000000000000..fea82365c1a0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode + +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.content.ContentResolver +import android.os.Binder +import android.provider.Settings +import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS +import android.testing.AndroidTestingRunner +import android.view.Display.DEFAULT_DISPLAY +import android.view.IWindowManager +import android.view.WindowManager.TRANSIT_CHANGE +import android.window.DisplayAreaInfo +import android.window.WindowContainerTransaction +import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.never +import com.android.wm.shell.MockToken +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.transition.Transitions +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.isNull +import org.mockito.Mock +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever + +/** + * Test class for [DesktopDisplayEventHandler] + * + * Usage: atest WMShellUnitTests:DesktopDisplayEventHandlerTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DesktopDisplayEventHandlerTest : ShellTestCase() { + + @Mock lateinit var testExecutor: ShellExecutor + @Mock lateinit var transitions: Transitions + @Mock lateinit var displayController: DisplayController + @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + @Mock private lateinit var mockWindowManager: IWindowManager + + private lateinit var shellInit: ShellInit + private lateinit var handler: DesktopDisplayEventHandler + + @Before + fun setUp() { + shellInit = spy(ShellInit(testExecutor)) + whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } + val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) + handler = + DesktopDisplayEventHandler( + context, + shellInit, + transitions, + displayController, + rootTaskDisplayAreaOrganizer, + mockWindowManager, + ) + shellInit.init() + } + + private fun testDisplayWindowingModeSwitch( + defaultWindowingMode: Int, + extendedDisplayEnabled: Boolean, + expectTransition: Boolean + ) { + val externalDisplayId = 100 + val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java) + verify(displayController).addDisplayWindowListener(captor.capture()) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode + whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode } + val settingsSession = ExtendedDisplaySettingsSession( + context.contentResolver, if (extendedDisplayEnabled) 1 else 0) + + settingsSession.use { + // The external display connected. + whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId)) + captor.value.onDisplayAdded(externalDisplayId) + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + // The external display disconnected. + whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) + .thenReturn(intArrayOf(DEFAULT_DISPLAY)) + captor.value.onDisplayRemoved(externalDisplayId) + + if (expectTransition) { + val arg = argumentCaptor<WindowContainerTransaction>() + verify(transitions, times(2)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) + assertThat(arg.firstValue.changes[tda.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + assertThat(arg.secondValue.changes[tda.token.asBinder()]?.windowingMode) + .isEqualTo(defaultWindowingMode) + } else { + verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull()) + } + } + } + + @Test + fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() { + testDisplayWindowingModeSwitch( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + extendedDisplayEnabled = false, + expectTransition = false + ) + } + + @Test + fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() { + testDisplayWindowingModeSwitch( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + extendedDisplayEnabled = true, + expectTransition = true + ) + } + + @Test + fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() { + testDisplayWindowingModeSwitch( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, + extendedDisplayEnabled = true, + expectTransition = false + ) + } + + private class ExtendedDisplaySettingsSession( + private val contentResolver: ContentResolver, + private val overrideValue: Int + ) : AutoCloseable { + private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS + private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0) + + init { Settings.Global.putInt(contentResolver, settingName, overrideValue) } + + override fun close() { + Settings.Global.putInt(contentResolver, settingName, initialValue) + } + } +}
\ No newline at end of file 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/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index b157d557c1d8..315a46fcbd7b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -1123,11 +1123,11 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun moveRunningTaskToDesktop_topActivityTranslucentWithStyleFloating_taskIsMovedToDesktop() { + fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() { val task = setUpFullscreenTask().apply { isTopActivityTransparent = true - isTopActivityStyleFloating = true + isTopActivityNoDisplay = true numActivities = 1 } @@ -1139,11 +1139,11 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun moveRunningTaskToDesktop_topActivityTranslucentWithoutStyleFloating_doesNothing() { + fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() { val task = setUpFullscreenTask().apply { isTopActivityTransparent = true - isTopActivityStyleFloating = false + isTopActivityNoDisplay = false numActivities = 1 } @@ -1153,20 +1153,41 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun moveRunningTaskToDesktop_systemUIActivity_doesNothing() { - val task = setUpFullscreenTask() - + fun moveRunningTaskToDesktop_systemUIActivityWithDisplay_doesNothing() { // Set task as systemUI package val systemUIPackageName = context.resources.getString( com.android.internal.R.string.config_systemUi) val baseComponent = ComponentName(systemUIPackageName, /* class */ "") - task.baseActivity = baseComponent + val task = + setUpFullscreenTask().apply { + baseActivity = baseComponent + isTopActivityNoDisplay = false + } controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) verifyEnterDesktopWCTNotExecuted() } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun moveRunningTaskToDesktop_systemUIActivityWithoutDisplay_doesNothing() { + // Set task as systemUI package + val systemUIPackageName = context.resources.getString( + com.android.internal.R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* class */ "") + val task = + setUpFullscreenTask().apply { + baseActivity = baseComponent + isTopActivityNoDisplay = true + } + + controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) + + val wct = getLatestEnterDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) + } + + @Test fun moveRunningTaskToDesktop_deviceSupported_taskIsMovedToDesktop() { val task = setUpFullscreenTask() @@ -2223,14 +2244,14 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun handleRequest_topActivityTransparentWithStyleFloating_returnSwitchToFreeformWCT() { + fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() { val freeformTask = setUpFreeformTask() markTaskVisible(freeformTask) val task = setUpFullscreenTask().apply { isTopActivityTransparent = true - isTopActivityStyleFloating = true + isTopActivityNoDisplay = true numActivities = 1 } @@ -2241,11 +2262,14 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun handleRequest_topActivityTransparentWithoutStyleFloating_returnSwitchToFullscreenWCT() { + fun handleRequest_topActivityTransparentWithDisplay_returnSwitchToFullscreenWCT() { + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + val task = setUpFreeformTask().apply { isTopActivityTransparent = true - isTopActivityStyleFloating = false + isTopActivityNoDisplay = false numActivities = 1 } @@ -2256,14 +2280,19 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun handleRequest_systemUIActivity_returnSwitchToFullscreenWCT() { - val task = setUpFreeformTask() + fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT() { + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) // Set task as systemUI package val systemUIPackageName = context.resources.getString( com.android.internal.R.string.config_systemUi) val baseComponent = ComponentName(systemUIPackageName, /* class */ "") - task.baseActivity = baseComponent + val task = + setUpFreeformTask().apply { + baseActivity = baseComponent + isTopActivityNoDisplay = false + } val result = controller.handleRequest(Binder(), createTransition(task)) assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) @@ -2271,6 +2300,27 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() { + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + + // Set task as systemUI package + val systemUIPackageName = context.resources.getString( + com.android.internal.R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* class */ "") + val task = + setUpFullscreenTask().apply { + baseActivity = baseComponent + isTopActivityNoDisplay = true + } + + val result = controller.handleRequest(Binder(), createTransition(task)) + assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + } + + @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() { val task = setUpFreeformTask() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java new file mode 100644 index 000000000000..a4008c1e6995 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java @@ -0,0 +1,201 @@ +/* + * 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.animation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Resources; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.Surface; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; +import com.android.wm.shell.pip2.phone.PipAppIconOverlay; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit test again {@link PipEnterAnimator}. + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner.class) +public class PipEnterAnimatorTest { + + @Mock private Context mMockContext; + + @Mock private Resources mMockResources; + + @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory; + + @Mock private SurfaceControl.Transaction mMockAnimateTransaction; + + @Mock private SurfaceControl.Transaction mMockStartTransaction; + + @Mock private SurfaceControl.Transaction mMockFinishTransaction; + + @Mock private Runnable mMockStartCallback; + + @Mock private Runnable mMockEndCallback; + + @Mock private PipAppIconOverlay mMockPipAppIconOverlay; + + @Mock private SurfaceControl mMockAppIconOverlayLeash; + + @Mock private ActivityInfo mMockActivityInfo; + + @Surface.Rotation private int mRotation; + private SurfaceControl mTestLeash; + private Rect mEndBounds; + private PipEnterAnimator mPipEnterAnimator; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mMockContext.getResources()).thenReturn(mMockResources); + when(mMockResources.getInteger(anyInt())).thenReturn(0); + when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction); + when(mMockAnimateTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) + .thenReturn(mMockAnimateTransaction); + when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) + .thenReturn(mMockStartTransaction); + when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) + .thenReturn(mMockFinishTransaction); + when(mMockPipAppIconOverlay.getLeash()).thenReturn(mMockAppIconOverlayLeash); + + mTestLeash = new SurfaceControl.Builder() + .setContainerLayer() + .setName("PipExpandAnimatorTest") + .setCallsite("PipExpandAnimatorTest") + .build(); + } + + @Test + public void setAnimationStartCallback_enter_callbackStartCallback() { + mRotation = Surface.ROTATION_0; + mEndBounds = new Rect(100, 100, 500, 500); + mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash, + mMockStartTransaction, mMockFinishTransaction, + mEndBounds, mRotation); + mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory); + + mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback); + mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mPipEnterAnimator.start(); + mPipEnterAnimator.pause(); + }); + + verify(mMockStartCallback).run(); + verifyZeroInteractions(mMockEndCallback); + } + + @Test + public void setAnimationEndCallback_enter_callbackStartAndEndCallback() { + mRotation = Surface.ROTATION_0; + mEndBounds = new Rect(100, 100, 500, 500); + mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash, + mMockStartTransaction, mMockFinishTransaction, + mEndBounds, mRotation); + mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory); + + mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback); + mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mPipEnterAnimator.start(); + mPipEnterAnimator.end(); + }); + + verify(mMockStartCallback).run(); + verify(mMockEndCallback).run(); + } + + @Test + public void setAppIconContentOverlay_thenGetContentOverlayLeash_returnOverlayLeash() { + mRotation = Surface.ROTATION_0; + mEndBounds = new Rect(100, 100, 500, 500); + mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash, + mMockStartTransaction, mMockFinishTransaction, + mEndBounds, mRotation); + mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory); + mPipEnterAnimator.setPipAppIconOverlaySupplier( + (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay); + + mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds, + mMockActivityInfo, 64 /* iconSize */); + + assertEquals(mPipEnterAnimator.getContentOverlayLeash(), mMockAppIconOverlayLeash); + } + + @Test + public void setAppIconContentOverlay_thenClearAppIconOverlay_returnNullLeash() { + mRotation = Surface.ROTATION_0; + mEndBounds = new Rect(100, 100, 500, 500); + mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash, + mMockStartTransaction, mMockFinishTransaction, + mEndBounds, mRotation); + mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory); + mPipEnterAnimator.setPipAppIconOverlaySupplier( + (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay); + + mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds, + mMockActivityInfo, 64 /* iconSize */); + mPipEnterAnimator.clearAppIconOverlay(); + + assertNull(mPipEnterAnimator.getContentOverlayLeash()); + } + + @Test + public void onEnterAnimationUpdate_withContentOverlay_animateOverlay() { + mRotation = Surface.ROTATION_0; + mEndBounds = new Rect(100, 100, 500, 500); + mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash, + mMockStartTransaction, mMockFinishTransaction, + mEndBounds, mRotation); + mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory); + mPipEnterAnimator.setPipAppIconOverlaySupplier( + (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay); + + float fraction = 0.5f; + mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds, + mMockActivityInfo, 64 /* iconSize */); + mPipEnterAnimator.onEnterAnimationUpdate(fraction, mMockAnimateTransaction); + + verify(mMockPipAppIconOverlay).onAnimationUpdate( + eq(mMockAnimateTransaction), anyFloat(), eq(fraction), eq(mEndBounds)); + } +} 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/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/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 03aab18d8d87..56267174ba75 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 @@ -478,25 +478,10 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() { + fun testDecorationIsNotCreatedForTopTranslucentActivities() { val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply { isTopActivityTransparent = true - isTopActivityStyleFloating = true - numActivities = 1 - } - doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) } - setUpMockDecorationsForTasks(task) - - onTaskOpening(task) - assertTrue(windowDecorByTaskIdSpy.contains(task.taskId)) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun testDecorationIsNotCreatedForTopTranslucentActivitiesWithoutStyleFloating() { - val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply { - isTopActivityTransparent = true - isTopActivityStyleFloating = false + isTopActivityNoDisplay = false numActivities = 1 } onTaskOpening(task) @@ -507,13 +492,14 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun testDecorationIsNotCreatedForSystemUIActivities() { - val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN) - // Set task as systemUI package val systemUIPackageName = context.resources.getString( com.android.internal.R.string.config_systemUi) val baseComponent = ComponentName(systemUIPackageName, /* class */ "") - task.baseActivity = baseComponent + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply { + baseActivity = baseComponent + isTopActivityNoDisplay = false + } onTaskOpening(task) 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/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/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig index 185f579df4b9..0adc4783445a 100644 --- a/media/java/android/media/flags/editing.aconfig +++ b/media/java/android/media/flags/editing.aconfig @@ -15,3 +15,10 @@ flag { description: "Enable B frames for Stagefright recorder." bug: "341121900" } + +flag { + name: "muxer_mp4_enable_apv" + namespace: "media_solutions" + description: "Enable APV support in mp4 writer." + bug: "370061501" +} diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 6658918518ea..abfc24413d56 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -16,6 +16,8 @@ package android.media.tv; +import static android.media.tv.flags.Flags.tifExtensionStandardization; + import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IntDef; @@ -159,6 +161,11 @@ public abstract class TvInputService extends Service { new RemoteCallbackList<>(); private TvInputManager mTvInputManager; + /** + * @hide + */ + protected TvInputServiceExtensionManager mTvInputServiceExtensionManager = + new TvInputServiceExtensionManager(); @Override public final IBinder onBind(Intent intent) { @@ -211,12 +218,23 @@ public abstract class TvInputService extends Service { } @Override - public List<String> getAvailableExtensionInterfaceNames() { - return TvInputService.this.getAvailableExtensionInterfaceNames(); + public List<String> getAvailableExtensionInterfaceNames() { + List<String> extensionNames = + TvInputService.this.getAvailableExtensionInterfaceNames(); + if (tifExtensionStandardization()) { + extensionNames.addAll( + TvInputServiceExtensionManager.getStandardExtensionInterfaceNames()); + } + return extensionNames; } @Override public IBinder getExtensionInterface(String name) { + if (tifExtensionStandardization() && name != null) { + if (TvInputServiceExtensionManager.checkIsStandardizedInterfaces(name)) { + return mTvInputServiceExtensionManager.getExtensionIBinder(name); + } + } return TvInputService.this.getExtensionInterface(name); } diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java new file mode 100644 index 000000000000..0e98488c93c0 --- /dev/null +++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java @@ -0,0 +1,614 @@ +/* + * 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.media.tv; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.media.tv.flags.Flags; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * @hide + */ +@FlaggedApi(Flags.FLAG_TIF_EXTENSION_STANDARDIZATION) +public 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."; + private static final String CAM_PACKAGE = "android.media.tv.extension.cam."; + private static final String RATING_PACKAGE = "android.media.tv.extension.rating."; + private static final String TIME_PACKAGE = "android.media.tv.extension.time."; + private static final String TELETEXT_PACKAGE = "android.media.tv.extension.teletext."; + private static final String SCAN_BSU_PACKAGE = "android.media.tv.extension.scanbsu."; + private static final String CLIENT_TOKEN_PACKAGE = "android.media.tv.extension.clienttoken."; + private static final String SCREEN_MODE_PACKAGE = "android.media.tv.extension.screenmode."; + private static final String SIGNAL_PACKAGE = "android.media.tv.extension.signal."; + private static final String SERVICE_DATABASE_PACKAGE = "android.media.tv.extension.servicedb."; + private static final String PVR_PACKAGE = "android.media.tv.extension.pvr."; + private static final String EVENT_PACKAGE = "android.media.tv.extension.event."; + 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 */ + public static final int REGISTER_SUCCESS = 0; + /** Register binder returns fail 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 */ + public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2; + /** Register binder returns fail when remote server not available */ + public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3; + + /** + * Interface responsible for creating scan session and obtain parameters. + */ + public static final String ISCAN_INTERFACE = SCAN_PACKAGE + "IScanInterface"; + /** + * Interface that handles scan session and get/store related information. + */ + public static final String ISCAN_SESSION = SCAN_PACKAGE + "IScanSession"; + /** + * Interface that notifies changes related to scan session. + */ + public static final String ISCAN_LISTENER = SCAN_PACKAGE + "IScanListener"; + /** + * Interface for setting HDPlus information. + */ + public static final String IHDPLUS_INFO = SCAN_PACKAGE + "IHDPlusInfo"; + /** + * Interface for handling operator detection for scanning. + */ + public static final String IOPERATOR_DETECTION = SCAN_PACKAGE + "IOperatorDetection"; + /** + * Interface for changes related to operator detection searches. + */ + public static final String IOPERATOR_DETECTION_LISTENER = SCAN_PACKAGE + + "IOperatorDetectionListener"; + /** + * Interface for handling region channel list for scanning. + */ + public static final String IREGION_CHANNEL_LIST = SCAN_PACKAGE + "IRegionChannelList"; + /** + * Interface for changes related to changes in region channel list search. + */ + public static final String IREGION_CHANNEL_LIST_LISTENER = SCAN_PACKAGE + + "IRegionChannelListListener"; + /** + * Interface for handling target region information. + */ + public static final String ITARGET_REGION = SCAN_PACKAGE + "ITargetRegion"; + /** + * Interface for changes related to target regions during scanning. + */ + public static final String ITARGET_REGION_LISTENER = SCAN_PACKAGE + "ITargetRegionListener"; + /** + * Interface for handling LCN conflict groups. + */ + public static final String ILCN_CONFLICT = SCAN_PACKAGE + "ILcnConflict"; + /** + * Interface for detecting LCN conflicts during scanning. + */ + public static final String ILCN_CONFLICT_LISTENER = SCAN_PACKAGE + "ILcnConflictListener"; + /** + * Interface for handling LCN V2 channel list information. + */ + public static final String ILCNV2_CHANNEL_LIST = SCAN_PACKAGE + "ILcnV2ChannelList"; + /** + * Interface for detecting LCN V2 channel list during scanning. + */ + public static final String ILCNV2_CHANNEL_LIST_LISTENER = SCAN_PACKAGE + + "ILcnV2ChannelListListener"; + /** + * Interface for handling favorite network related information. + */ + public static final String IFAVORITE_NETWORK = SCAN_PACKAGE + "IFavoriteNetwork"; + /** + * Interface for detecting favorite network during scanning. + */ + public static final String IFAVORITE_NETWORK_LISTENER = SCAN_PACKAGE + + "IFavoriteNetworkListener"; + /** + * Interface for handling Turksat channel update system service. + */ + public static final String ITKGS_INFO = SCAN_PACKAGE + "ITkgsInfo"; + /** + * Interface for changes related to TKGS information. + */ + public static final String ITKGS_INFO_LISTENER = SCAN_PACKAGE + "ITkgsInfoListener"; + /** + * Interface for satellite search related to low noise block downconverter. + */ + public static final String ISCAN_SAT_SEARCH = SCAN_PACKAGE + "IScanSatSearch"; + /** + * Interface for Over-the-Air Download. + */ + public static final String IOAD_UPDATE_INTERFACE = OAD_PACKAGE + "IOadUpdateInterface"; + /** + * Interface for handling conditional access module app related information. + */ + public static final String ICAM_APP_INFO_SERVICE = CAM_PACKAGE + "ICamAppInfoService"; + /** + * Interface for changes on conditional access module app related information. + */ + public static final String ICAM_APP_INFO_LISTENER = CAM_PACKAGE + "ICamAppInfoListener"; + /** + * Interface for handling conditional access module related information. + */ + public static final String ICAM_MONITORING_SERVICE = CAM_PACKAGE + "ICamMonitoringService"; + /** + * Interface for changes on conditional access module related information. + */ + public static final String ICAM_INFO_LISTENER = CAM_PACKAGE + "ICamInfoListener"; + /** + * Interface for handling control of CI+ operations. + */ + public static final String ICI_OPERATOR_INTERFACE = CAM_PACKAGE + "ICiOperatorInterface"; + /** + * Interfaces for changes on CI+ operations. + */ + public static final String ICI_OPERATOR_LISTENER = CAM_PACKAGE + "ICiOperatorListener"; + /** + * Interface for handling conditional access module profile related information. + */ + public static final String ICAM_PROFILE_INTERFACE = CAM_PACKAGE + "ICamProfileInterface"; + /** + * Interface for handling conditional access module DRM related information. + */ + public static final String ICONTENT_CONTROL_SERVICE = CAM_PACKAGE + "IContentControlService"; + /** + * Interface for changes on DRM. + */ + public static final String ICAM_DRM_INFO_LISTENER = CAM_PACKAGE + "ICamDrmInfoListener"; + /** + * Interface for handling conditional access module pin related information. + */ + public static final String ICAM_PIN_SERVICE = CAM_PACKAGE + "ICamPinService"; + /** + * Interface for changes on conditional access module pin capability. + */ + public static final String ICAM_PIN_CAPABILITY_LISTENER = CAM_PACKAGE + + "ICamPinCapabilityListener"; + /** + * Interface for changes on conditional access module pin status. + */ + public static final String ICAM_PIN_STATUS_LISTENER = CAM_PACKAGE + "ICamPinStatusListener"; + /** + * Interface for handling conditional access module host control service. + */ + public static final String ICAM_HOST_CONTROL_SERVICE = CAM_PACKAGE + "ICamHostControlService"; + /** + * Interface for handling conditional access module ask release reply. + */ + public static final String ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK = CAM_PACKAGE + + "ICamHostControlAskReleaseReplyCallback"; + /** + * Interface for changes on conditional access module host control service. + */ + public static final String ICAM_HOST_CONTROL_INFO_LISTENER = CAM_PACKAGE + + "ICamHostControlInfoListener"; + /** + * Interface for handling conditional access module host control service tune_quietly_flag. + */ + 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. + */ + public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER = CAM_PACKAGE + + "ICamHostControlTuneQuietlyFlagListener"; + /** + * Interface for handling conditional access module multi media interface. + */ + public static final String IMMI_INTERFACE = CAM_PACKAGE + "IMmiInterface"; + /** + * Interface for controlling conditional access module multi media session. + */ + public static final String IMMI_SESSION = CAM_PACKAGE + "IMmiSession"; + /** + * Interface for changes on conditional access module multi media session status. + */ + public static final String IMMI_STATUS_CALLBACK = CAM_PACKAGE + "IMmiStatusCallback"; + /** + * Interface for changes on conditional access app info related to entering menu. + */ + public static final String IENTER_MENU_ERROR_CALLBACK = CAM_PACKAGE + "IEnterMenuErrorCallback"; + /** + * Interface for handling RRT downloadable rating data. + */ + public static final String IDOWNLOADABLE_RATING_TABLE_MONITOR = RATING_PACKAGE + + "IDownloadableRatingTableMonitor"; + /** + * Interface for handling RRT rating related information. + */ + public static final String IRATING_INTERFACE = RATING_PACKAGE + "IRatingInterface"; + /** + * Interface for handling PMT rating related information. + */ + public static final String IPMT_RATING_INTERFACE = RATING_PACKAGE + "IPmtRatingInterface"; + /** + * Interface for changes on PMT rating related information. + */ + public static final String IPMT_RATING_LISTENER = RATING_PACKAGE + "IPmtRatingListener"; + /** + * Interface for handling IVBI rating related information. + */ + public static final String IVBI_RATING_INTERFACE = RATING_PACKAGE + "IVbiRatingInterface"; + /** + * Interface for changes on IVBI rating related information. + */ + public static final String IVBI_RATING_LISTENER = RATING_PACKAGE + "IVbiRatingListener"; + /** + * Interface for handling program rating related information. + */ + public static final String IPROGRAM_INFO = RATING_PACKAGE + "IProgramInfo"; + /** + * Interface for changes on program rating related information. + */ + public static final String IPROGRAM_INFO_LISTENER = RATING_PACKAGE + "IProgramInfoListener"; + /** + * Interface for getting broadcast time related information. + */ + public static final String BROADCAST_TIME = TIME_PACKAGE + "BroadcastTime"; + /** + * Interface for handling data service signal information on teletext. + */ + public static final String IDATA_SERVICE_SIGNAL_INFO = TELETEXT_PACKAGE + + "IDataServiceSignalInfo"; + /** + * Interface for changes on data service signal information on teletext. + */ + public static final String IDATA_SERVICE_SIGNAL_INFO_LISTENER = TELETEXT_PACKAGE + + "IDataServiceSignalInfoListener"; + /** + * Interface for handling teletext page information. + */ + public static final String ITELETEXT_PAGE_SUB_CODE = TELETEXT_PACKAGE + "ITeletextPageSubCode"; + /** + * Interface for handling scan background service update. + */ + public static final String ISCAN_BACKGROUND_SERVICE_UPDATE = SCAN_BSU_PACKAGE + + "IScanBackgroundServiceUpdate"; + /** + * Interface for changes on background service update + */ + public static final String ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER = SCAN_BSU_PACKAGE + + "IScanBackgroundServiceUpdateListener"; + /** + * Interface for generating client token. + */ + public static final String ICLIENT_TOKEN = CLIENT_TOKEN_PACKAGE + "IClientToken"; + /** + * Interfaces for handling screen mode information. + */ + public static final String ISCREEN_MODE_SETTINGS = SCREEN_MODE_PACKAGE + "IScreenModeSettings"; + /** + * Interfaces for handling HDMI signal information update. + */ + public static final String IHDMI_SIGNAL_INTERFACE = SIGNAL_PACKAGE + "IHdmiSignalInterface"; + /** + * Interfaces for changes on HDMI signal information update. + */ + public static final String IHDMI_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + + "IHdmiSignalInfoListener"; + /** + * Interfaces for handling audio signal information update. + */ + public static final String IAUDIO_SIGNAL_INFO = SIGNAL_PACKAGE + "IAudioSignalInfo"; + /** + * Interfaces for handling analog audio signal information update. + */ + public static final String IANALOG_AUDIO_INFO = SIGNAL_PACKAGE + "IAnalogAudioInfo"; + /** + * Interfaces for change on audio signal information update. + */ + public static final String IAUDIO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + + "IAudioSignalInfoListener"; + /** + * Interfaces for handling video signal information update. + */ + public static final String IVIDEO_SIGNAL_INFO = SIGNAL_PACKAGE + "IVideoSignalInfo"; + /** + * Interfaces for changes on video signal information update. + */ + public static final String IVIDEO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + + "IVideoSignalInfoListener"; + /** + * Interfaces for handling service database updates. + */ + public static final String ISERVICE_LIST_EDIT = SERVICE_DATABASE_PACKAGE + "IServiceListEdit"; + /** + * Interfaces for changes on service database updates. + */ + public static final String ISERVICE_LIST_EDIT_LISTENER = SERVICE_DATABASE_PACKAGE + + "IServiceListEditListener"; + /** + * Interfaces for getting service database related information. + */ + public static final String ISERVICE_LIST = SERVICE_DATABASE_PACKAGE + "IServiceList"; + /** + * Interfaces for transferring service database related information. + */ + public static final String ISERVICE_LIST_TRANSFER_INTERFACE = SERVICE_DATABASE_PACKAGE + + "IServiceListTransferInterface"; + /** + * Interfaces for exporting service database session. + */ + public static final String ISERVICE_LIST_EXPORT_SESSION = SERVICE_DATABASE_PACKAGE + + "IServiceListExportSession"; + /** + * Interfaces for changes on exporting service database session. + */ + public static final String ISERVICE_LIST_EXPORT_LISTENER = SERVICE_DATABASE_PACKAGE + + "IServiceListExportListener"; + /** + * Interfaces for importing service database session. + */ + public static final String ISERVICE_LIST_IMPORT_SESSION = SERVICE_DATABASE_PACKAGE + + "IServiceListImportSession"; + /** + * Interfaces for changes on importing service database session. + */ + public static final String ISERVICE_LIST_IMPORT_LISTENER = SERVICE_DATABASE_PACKAGE + + "IServiceListImportListener"; + /** + * Interfaces for setting channel list resources. + */ + public static final String ISERVICE_LIST_SET_CHANNEL_LIST_SESSION = SERVICE_DATABASE_PACKAGE + + "IServiceListSetChannelListSession"; + /** + * Interfaces for changes on setting channel list resources. + */ + public static final String ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER = SERVICE_DATABASE_PACKAGE + + "IServiceListSetChannelListListener"; + /** + * Interfaces for transferring channel list resources. + */ + public static final String ICHANNEL_LIST_TRANSFER = SERVICE_DATABASE_PACKAGE + + "IChannelListTransfer"; + /** + * Interfaces for record contents updates. + */ + public static final String IRECORDED_CONTENTS = PVR_PACKAGE + "IRecordedContents"; + /** + * Interfaces for changes on deleting record contents. + */ + public static final String IDELETE_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE + + "IDeleteRecordedContentsCallback"; + /** + * Interfaces for changes on getting record contents. + */ + public static final String IGET_INFO_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE + + "IGetInfoRecordedContentsCallback"; + /** + * Interfaces for monitoring present event information. + */ + public static final String IEVENT_MONITOR = EVENT_PACKAGE + "IEventMonitor"; + /** + * Interfaces for changes on present event information. + */ + public static final String IEVENT_MONITOR_LISTENER = EVENT_PACKAGE + "IEventMonitorListener"; + /** + * Interfaces for handling download event information. + */ + public static final String IEVENT_DOWNLOAD = EVENT_PACKAGE + "IEventDownload"; + /** + * Interfaces for changes on downloading event information. + */ + public static final String IEVENT_DOWNLOAD_LISTENER = EVENT_PACKAGE + "IEventDownloadListener"; + /** + * Interfaces for handling download event information for DVB and DTMB. + */ + public static final String IEVENT_DOWNLOAD_SESSION = EVENT_PACKAGE + "IEventDownloadSession"; + /** + * Interfaces for handling analog color system. + */ + public static final String IANALOG_ATTRIBUTE_INTERFACE = ANALOG_PACKAGE + + "IAnalogAttributeInterface"; + /** + * Interfaces for monitoring channel tuned information. + */ + public static final String ICHANNEL_TUNED_INTERFACE = TUNE_PACKAGE + "IChannelTunedInterface"; + /** + * Interfaces for changes on channel tuned information. + */ + public static final String ICHANNEL_TUNED_LISTENER = TUNE_PACKAGE + "IChannelTunedListener"; + /** + * Interfaces for handling tuner frontend signal info. + */ + public static final String ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE = SIGNAL_PACKAGE + + "ITunerFrontendSignalInfoInterface"; + /** + * Interfaces for changes on tuner frontend signal info. + */ + public static final String ITUNER_FRONTEND_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + + "ITunerFrontendSignalInfoListener"; + /** + * Interfaces for handling mux tune operations. + */ + public static final String IMUX_TUNE_SESSION = TUNE_PACKAGE + "IMuxTuneSession"; + /** + * Interfaces for initing mux tune session. + */ + public static final String IMUX_TUNE = TUNE_PACKAGE + "IMuxTune"; + + // Set of standardized AIDL interface canonical names + private static final Set<String> sTisExtensions = new HashSet<>(Set.of( + 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, + BROADCAST_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 + )); + + // Store the mapping between interface names and IBinder + private Map<String, IBinder> mExtensionInterfaceIBinderMapping = new HashMap<>(); + + TvInputServiceExtensionManager() { + } + + /** + * Function to return available extension interface names + * + * @hide + */ + public static @NonNull List<String> getStandardExtensionInterfaceNames() { + return new ArrayList<>(sTisExtensions); + } + + /** + * Function to check if the extension is in the standardization list + */ + static boolean checkIsStandardizedInterfaces(@NonNull String extensionName) { + return sTisExtensions.contains(extensionName); + } + + /** + * This function should be used by OEM to register IBinder objects that implement + * standardized AIDL interfaces. + * + * @param extensionName Extension Interface Name + * @param binder IBinder object to be registered + * @return REGISTER_SUCCESS on success of registering IBinder object + * REGISTER_FAIL_NAME_NOT_STANDARDIZED on failure due to registering extension with + * non-standardized name + * REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED on failure due to IBinder not + * implementing standardized AIDL interface + * REGISTER_FAIL_REMOTE_EXCEPTION on failure due to remote exception + * + * @hide + */ + public int registerExtensionIBinder(@NonNull String extensionName, + @NonNull IBinder binder) { + if (!checkIsStandardizedInterfaces(extensionName)) { + return REGISTER_FAIL_NAME_NOT_STANDARDIZED; + } + try { + if (binder.getInterfaceDescriptor().equals(extensionName)) { + mExtensionInterfaceIBinderMapping.put(extensionName, binder); + return REGISTER_SUCCESS; + } else { + return REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED; + } + } catch (RemoteException e) { + Log.e(TAG, "Fetching IBinder object failure due to " + e); + return REGISTER_FAIL_REMOTE_EXCEPTION; + } + } + + /** + * Function to get corresponding IBinder object + */ + @Nullable IBinder getExtensionIBinder(@NonNull String extensionName) { + return mExtensionInterfaceIBinderMapping.get(extensionName); + } + +} diff --git a/media/java/android/mtp/OWNERS b/media/java/android/mtp/OWNERS index 6b5336e83bdc..77ed08b1f9a5 100644 --- a/media/java/android/mtp/OWNERS +++ b/media/java/android/mtp/OWNERS @@ -1,10 +1,9 @@ set noparent -aprasath@google.com anothermark@google.com -kumarashishg@google.com -sarup@google.com +febinthattil@google.com +aprasath@google.com jsharkey@android.com jameswei@google.com rmojumder@google.com - +kumarashishg@google.com diff --git a/media/tests/MtpTests/OWNERS b/media/tests/MtpTests/OWNERS index 6b5336e83bdc..bdb6cdbea332 100644 --- a/media/tests/MtpTests/OWNERS +++ b/media/tests/MtpTests/OWNERS @@ -1,10 +1,9 @@ set noparent -aprasath@google.com anothermark@google.com -kumarashishg@google.com -sarup@google.com +febinthattil@google.com +aprasath@google.com jsharkey@android.com jameswei@google.com rmojumder@google.com - +kumarashishg@google.com
\ No newline at end of file diff --git a/nfc/api/current.txt b/nfc/api/current.txt index 96b7c1339190..008120429c40 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -207,6 +207,7 @@ package android.nfc.cardemulation { method public boolean isDefaultServiceForCategory(android.content.ComponentName, String); method @FlaggedApi("android.nfc.enable_card_emulation_euicc") public boolean isEuiccSupported(); method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>); + method @FlaggedApi("android.nfc.nfc_event_listener") public void registerNfcEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopPatternFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean); method public boolean removeAidsForService(android.content.ComponentName, String); @@ -216,6 +217,7 @@ package android.nfc.cardemulation { method public boolean setPreferredService(android.app.Activity, android.content.ComponentName); method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setShouldDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean); method public boolean supportsAidPrefixRegistration(); + method @FlaggedApi("android.nfc.nfc_event_listener") public void unregisterNfcEventListener(@NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener); method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName); method public boolean unsetPreferredService(android.app.Activity); field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT"; @@ -233,13 +235,16 @@ package android.nfc.cardemulation { field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0 } + @FlaggedApi("android.nfc.nfc_event_listener") public static interface CardEmulation.NfcEventListener { + method @FlaggedApi("android.nfc.nfc_event_listener") public default void onObserveModeStateChanged(boolean); + method @FlaggedApi("android.nfc.nfc_event_listener") public default void onPreferredServiceChanged(boolean); + } + public abstract class HostApduService extends android.app.Service { ctor public HostApduService(); method public final void notifyUnhandled(); method public final android.os.IBinder onBind(android.content.Intent); method public abstract void onDeactivated(int); - method @FlaggedApi("android.nfc.nfc_event_listener") public void onObserveModeStateChanged(boolean); - method @FlaggedApi("android.nfc.nfc_event_listener") public void onPreferredServiceChanged(boolean); method public abstract byte[] processCommandApdu(byte[], android.os.Bundle); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.nfc.cardemulation.PollingFrame>); method public final void sendResponseApdu(byte[]); diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index 24e14e69637b..6aa8a2b73f2c 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -91,6 +91,7 @@ package android.nfc { method public void onDisable(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public void onDisableFinished(int); method public void onDisableStarted(); + method public void onEeListenActivated(boolean); method public void onEnable(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public void onEnableFinished(int); method public void onEnableStarted(); @@ -105,7 +106,7 @@ package android.nfc { method public void onRfFieldActivated(boolean); method public void onRoutingChanged(); method public void onStateUpdated(int); - method public void onTagConnected(boolean, @NonNull android.nfc.Tag); + method public void onTagConnected(boolean); method public void onTagDispatch(@NonNull java.util.function.Consumer<java.lang.Boolean>); } diff --git a/nfc/java/android/nfc/ComponentNameAndUser.aidl b/nfc/java/android/nfc/ComponentNameAndUser.aidl new file mode 100644 index 000000000000..e677998a7970 --- /dev/null +++ b/nfc/java/android/nfc/ComponentNameAndUser.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nfc; + +parcelable ComponentNameAndUser;
\ No newline at end of file diff --git a/nfc/java/android/nfc/ComponentNameAndUser.java b/nfc/java/android/nfc/ComponentNameAndUser.java new file mode 100644 index 000000000000..59e6c62926c9 --- /dev/null +++ b/nfc/java/android/nfc/ComponentNameAndUser.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nfc; + +import android.annotation.UserIdInt; +import android.content.ComponentName; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * @hide + */ +public class ComponentNameAndUser implements Parcelable { + @UserIdInt private final int mUserId; + private ComponentName mComponentName; + + public ComponentNameAndUser(@UserIdInt int userId, ComponentName componentName) { + mUserId = userId; + mComponentName = componentName; + } + + /** + * @hide + */ + public int describeContents() { + return 0; + } + + /** + * @hide + */ + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mUserId); + out.writeParcelable(mComponentName, flags); + } + + public static final Parcelable.Creator<ComponentNameAndUser> CREATOR = + new Parcelable.Creator<ComponentNameAndUser>() { + public ComponentNameAndUser createFromParcel(Parcel in) { + return new ComponentNameAndUser(in); + } + + public ComponentNameAndUser[] newArray(int size) { + return new ComponentNameAndUser[size]; + } + }; + + private ComponentNameAndUser(Parcel in) { + mUserId = in.readInt(); + mComponentName = in.readParcelable(null, ComponentName.class); + } + + @UserIdInt + public int getUserId() { + return mUserId; + } + + public ComponentName getComponentName() { + return mComponentName; + } + + @Override + public String toString() { + return mComponentName + " for user id: " + mUserId; + } + + @Override + public boolean equals(Object obj) { + if (obj != null && obj instanceof ComponentNameAndUser) { + ComponentNameAndUser other = (ComponentNameAndUser) obj; + return other.getUserId() == mUserId + && Objects.equals(other.getComponentName(), mComponentName); + } + return false; + } + + @Override + public int hashCode() { + if (mComponentName == null) { + return mUserId; + } + return mComponentName.hashCode() + mUserId; + } +} diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl index 8535e4a9cfd2..5e2e92d958a4 100644 --- a/nfc/java/android/nfc/INfcCardEmulation.aidl +++ b/nfc/java/android/nfc/INfcCardEmulation.aidl @@ -17,6 +17,8 @@ package android.nfc; import android.content.ComponentName; +import android.nfc.INfcEventListener; + import android.nfc.cardemulation.AidGroup; import android.nfc.cardemulation.ApduServiceInfo; import android.os.RemoteCallback; @@ -55,4 +57,7 @@ interface INfcCardEmulation boolean isAutoChangeEnabled(); List<String> getRoutingStatus(); void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech, String sc); + + void registerNfcEventListener(in INfcEventListener listener); + void unregisterNfcEventListener(in INfcEventListener listener); } diff --git a/nfc/java/android/nfc/INfcEventListener.aidl b/nfc/java/android/nfc/INfcEventListener.aidl new file mode 100644 index 000000000000..5162c26ac536 --- /dev/null +++ b/nfc/java/android/nfc/INfcEventListener.aidl @@ -0,0 +1,11 @@ +package android.nfc; + +import android.nfc.ComponentNameAndUser; + +/** + * @hide + */ +oneway interface INfcEventListener { + void onPreferredServiceChanged(in ComponentNameAndUser ComponentNameAndUser); + void onObserveModeStateChanged(boolean isEnabled); +}
\ No newline at end of file diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl index 48c7ee659266..7f1fd15fe68a 100644 --- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl +++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl @@ -27,7 +27,7 @@ import java.util.List; * @hide */ interface INfcOemExtensionCallback { - void onTagConnected(boolean connected, in Tag tag); + void onTagConnected(boolean connected); void onStateUpdated(int state); void onApplyRouting(in ResultReceiver isSkipped); void onNdefRead(in ResultReceiver isSkipped); @@ -46,6 +46,7 @@ interface INfcOemExtensionCallback { void onCardEmulationActivated(boolean isActivated); void onRfFieldActivated(boolean isActivated); void onRfDiscoveryStarted(boolean isDiscoveryStarted); + void onEeListenActivated(boolean isActivated); void onGetOemAppSearchIntent(in List<String> firstPackage, in ResultReceiver intentConsumer); void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent); void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category); diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java index 474ff8c663e6..1d2085c88213 100644 --- a/nfc/java/android/nfc/NfcOemExtension.java +++ b/nfc/java/android/nfc/NfcOemExtension.java @@ -80,6 +80,7 @@ public final class NfcOemExtension { private boolean mCardEmulationActivated = false; private boolean mRfFieldActivated = false; private boolean mRfDiscoveryStarted = false; + private boolean mEeListenActivated = false; /** * Broadcast Action: Sent on NFC stack initialization when NFC OEM extensions are enabled. @@ -195,9 +196,8 @@ public final class NfcOemExtension { * ex - if tag is connected notify cover and Nfctest app if app is in testing mode * * @param connected status of the tag true if tag is connected otherwise false - * @param tag Tag details */ - void onTagConnected(boolean connected, @NonNull Tag tag); + void onTagConnected(boolean connected); /** * Update the Nfc Adapter State @@ -327,6 +327,13 @@ public final class NfcOemExtension { void onRfDiscoveryStarted(boolean isDiscoveryStarted); /** + * Notifies the NFCEE (NFC Execution Environment) Listen has been activated. + * + * @param isActivated true, if EE Listen is ON, else EE Listen is OFF. + */ + void onEeListenActivated(boolean isActivated); + + /** * Gets the intent to find the OEM package in the OEM App market. If the consumer returns * {@code null} or a timeout occurs, the intent from the first available package will be * used instead. @@ -437,6 +444,7 @@ public final class NfcOemExtension { callback.onCardEmulationActivated(mCardEmulationActivated); callback.onRfFieldActivated(mRfFieldActivated); callback.onRfDiscoveryStarted(mRfDiscoveryStarted); + callback.onEeListenActivated(mEeListenActivated); }); } } @@ -684,9 +692,9 @@ public final class NfcOemExtension { private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub { @Override - public void onTagConnected(boolean connected, Tag tag) throws RemoteException { + public void onTagConnected(boolean connected) throws RemoteException { mCallbackMap.forEach((cb, ex) -> - handleVoid2ArgCallback(connected, tag, cb::onTagConnected, ex)); + handleVoidCallback(connected, cb::onTagConnected, ex)); } @Override @@ -711,6 +719,13 @@ public final class NfcOemExtension { } @Override + public void onEeListenActivated(boolean isActivated) throws RemoteException { + mEeListenActivated = isActivated; + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(isActivated, cb::onEeListenActivated, ex)); + } + + @Override public void onStateUpdated(int state) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(state, cb::onStateUpdated, ex)); diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java index d75318f53fe3..9ff83fe77c9b 100644 --- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -52,10 +52,12 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.TreeMap; import java.util.regex.Pattern; /** @@ -204,7 +206,8 @@ public final class ApduServiceInfo implements Parcelable { this(info, onHost, description, staticAidGroups, dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid, settingsActivityName, offHost, staticOffHost, isEnabled, - new HashMap<String, Boolean>(), new HashMap<Pattern, Boolean>()); + new HashMap<String, Boolean>(), new TreeMap<>( + Comparator.comparing(Pattern::toString))); } /** @@ -340,7 +343,8 @@ public final class ApduServiceInfo implements Parcelable { mStaticAidGroups = new HashMap<String, AidGroup>(); mDynamicAidGroups = new HashMap<String, AidGroup>(); mAutoTransact = new HashMap<String, Boolean>(); - mAutoTransactPatterns = new HashMap<Pattern, Boolean>(); + mAutoTransactPatterns = new TreeMap<Pattern, Boolean>( + Comparator.comparing(Pattern::toString)); mOnHost = onHost; final int depth = parser.getDepth(); diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java index d8f04c50b695..eb28c3b9c930 100644 --- a/nfc/java/android/nfc/cardemulation/CardEmulation.java +++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java @@ -17,6 +17,7 @@ package android.nfc.cardemulation; import android.Manifest; +import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; @@ -33,15 +34,18 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.nfc.ComponentNameAndUser; import android.nfc.Constants; import android.nfc.Flags; import android.nfc.INfcCardEmulation; +import android.nfc.INfcEventListener; import android.nfc.NfcAdapter; import android.os.Build; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; +import android.util.ArrayMap; import android.util.Log; import java.lang.annotation.Retention; @@ -50,6 +54,8 @@ import java.util.HashMap; import java.util.HexFormat; import java.util.List; import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.Executor; import java.util.regex.Pattern; /** @@ -1076,4 +1082,107 @@ public final class CardEmulation { default -> throw new IllegalStateException("Unexpected value: " + route); }; } + + /** Listener for preferred service state changes. */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) + public interface NfcEventListener { + /** + * This method is called when this package gains or loses preferred Nfc service status, + * either the Default Wallet Role holder (see {@link + * android.app.role.RoleManager#ROLE_WALLET}) or the preferred service of the foreground + * activity set with {@link #setPreferredService(Activity, ComponentName)} + * + * @param isPreferred true is this service has become the preferred Nfc service, false if it + * is no longer the preferred service + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) + default void onPreferredServiceChanged(boolean isPreferred) {} + + /** + * This method is called when observe mode has been enabled or disabled. + * + * @param isEnabled true if observe mode has been enabled, false if it has been disabled + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) + default void onObserveModeStateChanged(boolean isEnabled) {} + } + + private final ArrayMap<NfcEventListener, Executor> mNfcEventListeners = new ArrayMap<>(); + + final INfcEventListener mINfcEventListener = + new INfcEventListener.Stub() { + public void onPreferredServiceChanged(ComponentNameAndUser componentNameAndUser) { + if (!android.nfc.Flags.nfcEventListener()) { + return; + } + boolean isPreferred = + componentNameAndUser != null + && componentNameAndUser.getUserId() + == mContext.getUser().getIdentifier() + && componentNameAndUser.getComponentName() != null + && Objects.equals( + mContext.getPackageName(), + componentNameAndUser.getComponentName() + .getPackageName()); + synchronized (mNfcEventListeners) { + mNfcEventListeners.forEach( + (listener, executor) -> { + executor.execute( + () -> listener.onPreferredServiceChanged(isPreferred)); + }); + } + } + + public void onObserveModeStateChanged(boolean isEnabled) { + if (!android.nfc.Flags.nfcEventListener()) { + return; + } + synchronized (mNfcEventListeners) { + mNfcEventListeners.forEach( + (listener, executor) -> { + executor.execute( + () -> listener.onObserveModeStateChanged(isEnabled)); + }); + } + } + }; + + /** + * Register a listener for NFC Events. + * + * @param executor The Executor to run the call back with + * @param listener The listener to register + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) + public void registerNfcEventListener( + @NonNull @CallbackExecutor Executor executor, @NonNull NfcEventListener listener) { + if (!android.nfc.Flags.nfcEventListener()) { + return; + } + synchronized (mNfcEventListeners) { + mNfcEventListeners.put(listener, executor); + if (mNfcEventListeners.size() == 1) { + callService(() -> sService.registerNfcEventListener(mINfcEventListener)); + } + } + } + + /** + * Unregister a preferred service listener that was previously registered with {@link + * #registerNfcEventListener(Executor, NfcEventListener)} + * + * @param listener The previously registered listener to unregister + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) + public void unregisterNfcEventListener(@NonNull NfcEventListener listener) { + if (!android.nfc.Flags.nfcEventListener()) { + return; + } + synchronized (mNfcEventListeners) { + mNfcEventListeners.remove(listener); + if (mNfcEventListeners.size() == 0) { + callService(() -> sService.unregisterNfcEventListener(mINfcEventListener)); + } + } + } } diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java index cd8e19c54565..4f601f0704b4 100644 --- a/nfc/java/android/nfc/cardemulation/HostApduService.java +++ b/nfc/java/android/nfc/cardemulation/HostApduService.java @@ -239,15 +239,6 @@ public abstract class HostApduService extends Service { */ public static final int MSG_POLLING_LOOP = 4; - /** - * @hide - */ - public static final int MSG_OBSERVE_MODE_CHANGE = 5; - - /** - * @hide - */ - public static final int MSG_PREFERRED_SERVICE_CHANGED = 6; /** * @hide @@ -343,16 +334,6 @@ public abstract class HostApduService extends Service { processPollingFrames(pollingFrames); } break; - case MSG_OBSERVE_MODE_CHANGE: - if (android.nfc.Flags.nfcEventListener()) { - onObserveModeStateChanged(msg.arg1 == 1); - } - break; - case MSG_PREFERRED_SERVICE_CHANGED: - if (android.nfc.Flags.nfcEventListener()) { - onPreferredServiceChanged(msg.arg1 == 1); - } - break; default: super.handleMessage(msg); } @@ -462,25 +443,4 @@ public abstract class HostApduService extends Service { */ public abstract void onDeactivated(int reason); - - /** - * This method is called when this service is the preferred Nfc service and - * Observe mode has been enabled or disabled. - * - * @param isEnabled true if observe mode has been enabled, false if it has been disabled - */ - @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) - public void onObserveModeStateChanged(boolean isEnabled) { - - } - - /** - * This method is called when this service gains or loses preferred Nfc service status. - * - * @param isPreferred true is this service has become the preferred Nfc service, - * false if it is no longer the preferred service - */ - @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) - public void onPreferredServiceChanged(boolean isPreferred) { - } } diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto index cbe602e00406..6b93cd73164f 100644 --- a/packages/SettingsLib/Graph/graph.proto +++ b/packages/SettingsLib/Graph/graph.proto @@ -77,6 +77,8 @@ message PreferenceProto { // Intent to show and locate the preference (might have highlight animation on // the preference). optional IntentProto launch_intent = 14; + // Descriptor of the preference value. + optional PreferenceValueDescriptorProto value_descriptor = 15; // Target of an Intent message ActionTarget { @@ -103,9 +105,28 @@ message TextProto { message PreferenceValueProto { oneof value { bool boolean_value = 1; + int32 int_value = 2; } } +// Proto of preference value descriptor. +message PreferenceValueDescriptorProto { + oneof type { + bool boolean_type = 1; + RangeValueProto range_value = 2; + } +} + +// Proto of preference value that is between a range. +message RangeValueProto { + // The lower bound (inclusive) of the range. + optional int32 min = 1; + // The upper bound (inclusive) of the range. + optional int32 max = 2; + // The increment step within the range. 0 means unset, which implies step size is 1. + optional int32 step = 3; +} + // Proto of android.content.Intent message IntentProto { // The action of the Intent. diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt index fdffe5de3f53..5ceee6d09978 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt @@ -65,6 +65,7 @@ constructor( val visitedScreens: Set<String> = setOf(), val locale: Locale? = null, val includeValue: Boolean = true, + val includeValueDescriptor: Boolean = true, ) object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> { diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt index cf6bf7012ac2..2256bb38dd2c 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt @@ -49,6 +49,7 @@ import com.android.settingslib.metadata.PreferenceScreenMetadata import com.android.settingslib.metadata.PreferenceScreenRegistry import com.android.settingslib.metadata.PreferenceSummaryProvider import com.android.settingslib.metadata.PreferenceTitleProvider +import com.android.settingslib.metadata.RangeValue import com.android.settingslib.preference.PreferenceScreenFactory import com.android.settingslib.preference.PreferenceScreenProvider import java.util.Locale @@ -66,6 +67,7 @@ private constructor(private val context: Context, private val request: GetPrefer private val builder by lazy { PreferenceGraphProto.newBuilder() } private val visitedScreens = mutableSetOf<String>().apply { addAll(request.visitedScreens) } private val includeValue = request.includeValue + private val includeValueDescriptor = request.includeValueDescriptor private suspend fun init() { for (key in request.screenKeys) { @@ -284,14 +286,37 @@ private constructor(private val context: Context, private val request: GetPrefer restricted = metadata.isRestricted(context) } persistent = metadata.isPersistent(context) - if ( - includeValue && - persistent && - metadata is BooleanValue && - metadata is PersistentPreference<*> - ) { - metadata.storage(context).getValue(metadata.key, Boolean::class.javaObjectType)?.let { - value = preferenceValueProto { booleanValue = it } + if (persistent) { + if (includeValue && metadata is PersistentPreference<*>) { + value = preferenceValueProto { + when (metadata) { + is BooleanValue -> + metadata + .storage(context) + .getValue(metadata.key, Boolean::class.javaObjectType) + ?.let { booleanValue = it } + is RangeValue -> { + metadata + .storage(context) + .getValue(metadata.key, Int::class.javaObjectType) + ?.let { intValue = it } + } + else -> {} + } + } + } + if (includeValueDescriptor) { + valueDescriptor = preferenceValueDescriptorProto { + when (metadata) { + is BooleanValue -> booleanType = true + is RangeValue -> rangeValue = rangeValueProto { + min = metadata.getMinValue(context) + max = metadata.getMaxValue(context) + step = metadata.getIncrementStep(context) + } + else -> {} + } + } } } if (metadata is PreferenceScreenMetadata) { diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt index d8db1bb776f5..6e4db1d90484 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt @@ -27,8 +27,10 @@ import com.android.settingslib.ipc.MessageCodec import com.android.settingslib.metadata.BooleanValue import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceAvailabilityProvider +import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.PreferenceRestrictionProvider import com.android.settingslib.metadata.PreferenceScreenRegistry +import com.android.settingslib.metadata.RangeValue import com.android.settingslib.metadata.ReadWritePermit /** Request to set preference value. */ @@ -114,27 +116,39 @@ class PreferenceSetterApiHandler(override val id: Int) : ApiHandler<PreferenceSe if (metadata is PreferenceAvailabilityProvider && !metadata.isAvailable(application)) { return PreferenceSetterResult.UNAVAILABLE } + + fun <T> PreferenceMetadata.checkWritePermit(value: T): Int { + @Suppress("UNCHECKED_CAST") val preference = (this as PersistentPreference<T>) + return when (preference.getWritePermit(application, value, myUid, callingUid)) { + ReadWritePermit.ALLOW -> PreferenceSetterResult.OK + ReadWritePermit.DISALLOW -> PreferenceSetterResult.DISALLOW + ReadWritePermit.REQUIRE_APP_PERMISSION -> + PreferenceSetterResult.REQUIRE_APP_PERMISSION + ReadWritePermit.REQUIRE_USER_AGREEMENT -> + PreferenceSetterResult.REQUIRE_USER_AGREEMENT + else -> PreferenceSetterResult.INTERNAL_ERROR + } + } + val storage = metadata.storage(application) val value = request.value try { if (value.hasBooleanValue()) { if (metadata !is BooleanValue) return PreferenceSetterResult.INVALID_REQUEST val booleanValue = value.booleanValue - @Suppress("UNCHECKED_CAST") - val booleanPreference = metadata as PersistentPreference<Boolean> - when ( - booleanPreference.getWritePermit(application, booleanValue, myUid, callingUid) - ) { - ReadWritePermit.ALLOW -> {} - ReadWritePermit.DISALLOW -> return PreferenceSetterResult.DISALLOW - ReadWritePermit.REQUIRE_APP_PERMISSION -> - return PreferenceSetterResult.REQUIRE_APP_PERMISSION - ReadWritePermit.REQUIRE_USER_AGREEMENT -> - return PreferenceSetterResult.REQUIRE_USER_AGREEMENT - else -> return PreferenceSetterResult.INTERNAL_ERROR - } + val resultCode = metadata.checkWritePermit(booleanValue) + if (resultCode != PreferenceSetterResult.OK) return resultCode storage.setValue(key, Boolean::class.javaObjectType, booleanValue) return PreferenceSetterResult.OK + } else if (value.hasIntValue()) { + val intValue = value.intValue + val resultCode = metadata.checkWritePermit(intValue) + if (resultCode != PreferenceSetterResult.OK) return resultCode + if (metadata is RangeValue && !metadata.isValidValue(application, intValue)) { + return PreferenceSetterResult.INVALID_REQUEST + } + storage.setValue(key, Int::class.javaObjectType, intValue) + return PreferenceSetterResult.OK } } catch (e: Exception) { return PreferenceSetterResult.INTERNAL_ERROR diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt index d7dae7771acd..dee32d9ed80e 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt @@ -24,7 +24,9 @@ import com.android.settingslib.graph.proto.PreferenceOrGroupProto import com.android.settingslib.graph.proto.PreferenceProto import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget import com.android.settingslib.graph.proto.PreferenceScreenProto +import com.android.settingslib.graph.proto.PreferenceValueDescriptorProto import com.android.settingslib.graph.proto.PreferenceValueProto +import com.android.settingslib.graph.proto.RangeValueProto import com.android.settingslib.graph.proto.TextProto /** Returns root or null. */ @@ -89,6 +91,16 @@ inline fun actionTargetProto(init: ActionTarget.Builder.() -> Unit) = inline fun preferenceValueProto(init: PreferenceValueProto.Builder.() -> Unit) = PreferenceValueProto.newBuilder().also(init).build() +/** Kotlin DSL-style builder for [PreferenceValueDescriptorProto]. */ +@JvmSynthetic +inline fun preferenceValueDescriptorProto(init: PreferenceValueDescriptorProto.Builder.() -> Unit) = + PreferenceValueDescriptorProto.newBuilder().also(init).build() + +/** Kotlin DSL-style builder for [RangeValueProto]. */ +@JvmSynthetic +inline fun rangeValueProto(init: RangeValueProto.Builder.() -> Unit) = + RangeValueProto.newBuilder().also(init).build() + /** Kotlin DSL-style builder for [TextProto]. */ @JvmSynthetic inline fun textProto(init: TextProto.Builder.() -> Unit) = TextProto.newBuilder().also(init).build() diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml index cc42dab6737e..944bef6c9e09 100644 --- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml +++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml @@ -21,6 +21,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:paddingVertical="@dimen/settingslib_expressive_space_small1" + android:paddingHorizontal="@dimen/settingslib_expressive_space_small1" android:filterTouchesWhenObscured="false"> <TextView @@ -40,7 +41,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@android:id/title" - android:layout_alignLeft="@android:id/title" android:layout_alignStart="@android:id/title" android:layout_gravity="start" android:textAlignment="viewStart" diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt index c3b1a7cb16e3..aeea8cb6df1b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt @@ -24,6 +24,7 @@ import android.media.AudioManager import android.util.Log import com.android.settingslib.volume.shared.model.AudioManagerEvent import com.android.settingslib.volume.shared.model.AudioStream +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharedFlow @@ -31,6 +32,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch @@ -44,6 +46,7 @@ interface AudioManagerEventsReceiver { class AudioManagerEventsReceiverImpl( private val context: Context, coroutineScope: CoroutineScope, + backgroundCoroutineContext: CoroutineContext, ) : AudioManagerEventsReceiver { private val allActions: Collection<String> @@ -79,6 +82,7 @@ class AudioManagerEventsReceiverImpl( .filterNotNull() .filter { intent -> allActions.contains(intent.action) } .mapNotNull { it.toAudioManagerEvent() } + .flowOn(backgroundCoroutineContext) .shareIn(coroutineScope, SharingStarted.WhileSubscribed()) private fun Intent.toAudioManagerEvent(): AudioManagerEvent? { diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt index 35ee8287d52f..58a09fbacc59 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt @@ -60,7 +60,12 @@ class AudioManagerEventsReceiverTest { fun setup() { MockitoAnnotations.initMocks(this) - underTest = AudioManagerEventsReceiverImpl(context, testScope.backgroundScope) + underTest = + AudioManagerEventsReceiverImpl( + context, + testScope.backgroundScope, + testScope.testScheduler, + ) } @Test diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index a18b6c1b301a..bffda8bcae65 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -536,6 +536,8 @@ android_library { "androidx.room_room-runtime", "androidx.room_room-ktx", "androidx.datastore_datastore-preferences", + "androidx.media3.media3-common", + "androidx.media3.media3-session", "com.google.android.material_material", "device_state_flags_lib", "kotlinx_coroutines_android", @@ -703,6 +705,8 @@ android_library { "androidx.room_room-testing", "androidx.room_room-ktx", "androidx.datastore_datastore-preferences", + "androidx.media3.media3-common", + "androidx.media3.media3-session", "device_state_flags_lib", "kotlinx-coroutines-android", "kotlinx-coroutines-core", 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 5c5edb1d00ba..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." @@ -468,6 +478,15 @@ flag { } flag { + name: "status_bar_notification_chips_test" + namespace: "systemui" + description: "Flag to enable certain features that let us test the status bar notification " + "chips with teamfooders. This flag should *never* be released to trunkfood or nextfood." + bug: "361346412" +} + + +flag { name: "compose_bouncer" namespace: "systemui" description: "Use the new compose bouncer in SystemUI" @@ -1515,6 +1534,16 @@ flag { } flag { + namespace: "systemui" + name: "user_aware_settings_repositories" + description: "Provide user-aware versions of SecureSettingsRepository and SystemSettingsRepository in SystemUI modules (see doc linked from b/356099784)." + bug: "356099784" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "notify_password_text_view_user_activity_in_background" namespace: "systemui" description: "Decide whether to notify the user activity in password text view, to power manager in the background thread." @@ -1559,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" @@ -1707,4 +1743,3 @@ flag { description: "An implementation of shortcut customizations through shortcut helper." bug: "365064144" } - 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/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index 6fc51e4d0f65..e78862e0e922 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -53,6 +53,7 @@ import com.android.systemui.notifications.ui.composable.ConstrainedNotificationS import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace import com.android.systemui.res.R import com.android.systemui.shade.LargeScreenHeaderHelper +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 @@ -84,7 +85,7 @@ constructor( stackScrollLayout: NotificationStackScrollLayout, sharedNotificationContainerBinder: SharedNotificationContainerBinder, private val keyguardRootViewModel: KeyguardRootViewModel, - private val configurationState: ConfigurationState, + @ShadeDisplayAware private val configurationState: ConfigurationState, private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker, private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore, @@ -127,7 +128,7 @@ constructor( } val burnIn = rememberBurnIn(clockInteractor) AnimatedVisibility( - visibleState = transitionState, + visibleState = transitionState, enter = fadeIn(), exit = fadeOut(), modifier = @@ -150,7 +151,7 @@ constructor( ) } } - }, + } ) } } @@ -172,7 +173,7 @@ constructor( areNotificationsVisible: Boolean, isShadeLayoutWide: Boolean, burnInParams: BurnInParameters?, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { if (!areNotificationsVisible) { return @@ -192,10 +193,7 @@ constructor( if (burnInParams == null) { it } else { - it.burnInAware( - viewModel = aodBurnInViewModel, - params = burnInParams, - ) + it.burnInAware(viewModel = aodBurnInViewModel, params = burnInParams) } }, ) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt index 2a91bd8b1d73..26c827a5417c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt @@ -43,6 +43,7 @@ import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace import com.android.systemui.qs.composefragment.ui.GridAnchor import com.android.systemui.qs.panels.ui.compose.EditMode import com.android.systemui.qs.panels.ui.compose.TileGrid @@ -53,8 +54,11 @@ 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.OverlayShade +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import com.android.systemui.statusbar.phone.ui.StatusBarIconController import com.android.systemui.statusbar.phone.ui.TintedIconManager +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -67,6 +71,8 @@ constructor( private val tintedIconManagerFactory: TintedIconManager.Factory, private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, private val statusBarIconController: StatusBarIconController, + private val notificationStackScrollView: Lazy<NotificationScrollView>, + private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory, ) : Overlay { override val key = Overlays.QuickSettingsShade @@ -98,6 +104,14 @@ constructor( ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel) } + + SnoozeableHeadsUpNotificationSpace( + stackScrollView = notificationStackScrollView.get(), + viewModel = + rememberViewModel("QuickSettingsShadeOverlay") { + notificationsPlaceholderViewModelFactory.create() + }, + ) } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 7872ffad6cec..041cd15bdeea 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -36,12 +36,11 @@ internal typealias SuspendedValue<T> = suspend () -> T internal interface DraggableHandler { /** - * Start a drag in the given [startedPosition], with the given [overSlop] and number of - * [pointersDown]. + * Start a drag with the given [pointersInfo] and [overSlop]. * * The returned [DragController] should be used to continue or stop the drag. */ - fun onDragStarted(startedPosition: Offset?, overSlop: Float, pointersDown: Int): DragController + fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController } /** @@ -96,7 +95,7 @@ internal class DraggableHandlerImpl( * Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f, * indicating that the transition should be intercepted. */ - internal fun shouldImmediatelyIntercept(startedPosition: Offset?): Boolean { + internal fun shouldImmediatelyIntercept(pointersInfo: PointersInfo?): Boolean { // We don't intercept the touch if we are not currently driving the transition. val dragController = dragController if (dragController?.isDrivingTransition != true) { @@ -107,7 +106,7 @@ internal class DraggableHandlerImpl( // Only intercept the current transition if one of the 2 swipes results is also a transition // between the same pair of contents. - val swipes = computeSwipes(startedPosition, pointersDown = 1) + val swipes = computeSwipes(pointersInfo) val fromContent = layoutImpl.content(swipeAnimation.currentContent) val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent) val currentScene = layoutImpl.state.currentScene @@ -124,11 +123,7 @@ internal class DraggableHandlerImpl( )) } - override fun onDragStarted( - startedPosition: Offset?, - overSlop: Float, - pointersDown: Int, - ): DragController { + override fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController { if (overSlop == 0f) { val oldDragController = dragController check(oldDragController != null && oldDragController.isDrivingTransition) { @@ -153,7 +148,7 @@ internal class DraggableHandlerImpl( return updateDragController(swipes, swipeAnimation) } - val swipes = computeSwipes(startedPosition, pointersDown) + val swipes = computeSwipes(pointersInfo) val fromContent = layoutImpl.contentForUserActions() swipes.updateSwipesResults(fromContent) @@ -190,8 +185,7 @@ internal class DraggableHandlerImpl( return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation) } - internal fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? { - if (startedPosition == null) return null + internal fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? { return layoutImpl.swipeSourceDetector.source( layoutSize = layoutImpl.lastSize, position = startedPosition.round(), @@ -200,57 +194,44 @@ internal class DraggableHandlerImpl( ) } - internal fun resolveSwipe( - pointersDown: Int, - fromSource: SwipeSource.Resolved?, - isUpOrLeft: Boolean, - ): Swipe.Resolved { - return Swipe.Resolved( - direction = - when (orientation) { - Orientation.Horizontal -> - if (isUpOrLeft) { - SwipeDirection.Resolved.Left - } else { - SwipeDirection.Resolved.Right - } - - Orientation.Vertical -> - if (isUpOrLeft) { - SwipeDirection.Resolved.Up - } else { - SwipeDirection.Resolved.Down - } - }, - pointerCount = pointersDown, - fromSource = fromSource, + private fun computeSwipes(pointersInfo: PointersInfo?): Swipes { + val fromSource = pointersInfo?.let { resolveSwipeSource(it.startedPosition) } + return Swipes( + upOrLeft = resolveSwipe(orientation, isUpOrLeft = true, pointersInfo, fromSource), + downOrRight = resolveSwipe(orientation, isUpOrLeft = false, pointersInfo, fromSource), ) } +} - private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes { - val fromSource = resolveSwipeSource(startedPosition) - val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true) - val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false) - return if (fromSource == null) { - Swipes( - upOrLeft = null, - downOrRight = null, - upOrLeftNoSource = upOrLeft, - downOrRightNoSource = downOrRight, - ) - } else { - Swipes( - upOrLeft = upOrLeft, - downOrRight = downOrRight, - upOrLeftNoSource = upOrLeft.copy(fromSource = null), - downOrRightNoSource = downOrRight.copy(fromSource = null), - ) - } - } +private fun resolveSwipe( + orientation: Orientation, + isUpOrLeft: Boolean, + pointersInfo: PointersInfo?, + fromSource: SwipeSource.Resolved?, +): Swipe.Resolved { + return Swipe.Resolved( + direction = + when (orientation) { + Orientation.Horizontal -> + if (isUpOrLeft) { + SwipeDirection.Resolved.Left + } else { + SwipeDirection.Resolved.Right + } - companion object { - private const val TAG = "DraggableHandlerImpl" - } + Orientation.Vertical -> + if (isUpOrLeft) { + SwipeDirection.Resolved.Up + } else { + SwipeDirection.Resolved.Down + } + }, + // If the number of pointers is not specified, 1 is assumed. + pointerCount = pointersInfo?.pointersDown ?: 1, + // Resolves the pointer type only if all pointers are of the same type. + pointersType = pointersInfo?.pointersDownByType?.keys?.singleOrNull(), + fromSource = fromSource, + ) } /** @param swipes The [Swipes] associated to the current gesture. */ @@ -498,24 +479,14 @@ private class DragControllerImpl( } /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */ -internal class Swipes( - val upOrLeft: Swipe.Resolved?, - val downOrRight: Swipe.Resolved?, - val upOrLeftNoSource: Swipe.Resolved?, - val downOrRightNoSource: Swipe.Resolved?, -) { +internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resolved) { /** The [UserActionResult] associated to up and down swipes. */ var upOrLeftResult: UserActionResult? = null var downOrRightResult: UserActionResult? = null fun computeSwipesResults(fromContent: Content): Pair<UserActionResult?, UserActionResult?> { - val userActions = fromContent.userActions - fun result(swipe: Swipe.Resolved?): UserActionResult? { - return userActions[swipe ?: return null] - } - - val upOrLeftResult = result(upOrLeft) ?: result(upOrLeftNoSource) - val downOrRightResult = result(downOrRight) ?: result(downOrRightNoSource) + val upOrLeftResult = fromContent.findActionResultBestMatch(swipe = upOrLeft) + val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight) return upOrLeftResult to downOrRightResult } @@ -569,11 +540,13 @@ internal class NestedScrollHandlerImpl( val connection: PriorityNestedScrollConnection = nestedScrollConnection() - private fun PointersInfo.resolveSwipe(isUpOrLeft: Boolean): Swipe.Resolved { - return draggableHandler.resolveSwipe( - pointersDown = pointersDown, - fromSource = draggableHandler.resolveSwipeSource(startedPosition), + private fun resolveSwipe(isUpOrLeft: Boolean, pointersInfo: PointersInfo?): Swipe.Resolved { + return resolveSwipe( + orientation = draggableHandler.orientation, isUpOrLeft = isUpOrLeft, + pointersInfo = pointersInfo, + fromSource = + pointersInfo?.let { draggableHandler.resolveSwipeSource(it.startedPosition) }, ) } @@ -582,12 +555,7 @@ internal class NestedScrollHandlerImpl( // moving on to the next scene. var canChangeScene = false - var _lastPointersInfo: PointersInfo? = null - fun pointersInfo(): PointersInfo { - return checkNotNull(_lastPointersInfo) { - "PointersInfo should be initialized before the transition begins." - } - } + var lastPointersInfo: PointersInfo? = null fun hasNextScene(amount: Float): Boolean { val transitionState = layoutState.transitionState @@ -595,17 +563,11 @@ internal class NestedScrollHandlerImpl( val fromScene = layoutImpl.scene(scene) val resolvedSwipe = when { - amount < 0f -> pointersInfo().resolveSwipe(isUpOrLeft = true) - amount > 0f -> pointersInfo().resolveSwipe(isUpOrLeft = false) + amount < 0f -> resolveSwipe(isUpOrLeft = true, lastPointersInfo) + amount > 0f -> resolveSwipe(isUpOrLeft = false, lastPointersInfo) else -> null } - val nextScene = - resolvedSwipe?.let { - fromScene.userActions[it] - ?: if (it.fromSource != null) { - fromScene.userActions[it.copy(fromSource = null)] - } else null - } + val nextScene = resolvedSwipe?.let { fromScene.findActionResultBestMatch(it) } if (nextScene != null) return true if (transitionState !is TransitionState.Idle) return false @@ -619,13 +581,14 @@ internal class NestedScrollHandlerImpl( return PriorityNestedScrollConnection( orientation = orientation, canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ -> + val pointersInfo = pointersInfoOwner.pointersInfo() canChangeScene = if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f val canInterceptSwipeTransition = canChangeScene && offsetAvailable != 0f && - draggableHandler.shouldImmediatelyIntercept(startedPosition = null) + draggableHandler.shouldImmediatelyIntercept(pointersInfo) if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false val threshold = layoutImpl.transitionInterceptionThreshold @@ -636,13 +599,11 @@ internal class NestedScrollHandlerImpl( return@PriorityNestedScrollConnection false } - val pointersInfo = pointersInfoOwner.pointersInfo() - - if (pointersInfo.isMouseWheel) { + if (pointersInfo?.isMouseWheel == true) { // Do not support mouse wheel interactions return@PriorityNestedScrollConnection false } - _lastPointersInfo = pointersInfo + lastPointersInfo = pointersInfo // If the current swipe transition is *not* closed to 0f or 1f, then we want the // scroll events to intercept the current transition to continue the scene @@ -662,11 +623,11 @@ internal class NestedScrollHandlerImpl( if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f val pointersInfo = pointersInfoOwner.pointersInfo() - if (pointersInfo.isMouseWheel) { + if (pointersInfo?.isMouseWheel == true) { // Do not support mouse wheel interactions return@PriorityNestedScrollConnection false } - _lastPointersInfo = pointersInfo + lastPointersInfo = pointersInfo val canStart = when (behavior) { @@ -704,11 +665,11 @@ internal class NestedScrollHandlerImpl( canChangeScene = false val pointersInfo = pointersInfoOwner.pointersInfo() - if (pointersInfo.isMouseWheel) { + if (pointersInfo?.isMouseWheel == true) { // Do not support mouse wheel interactions return@PriorityNestedScrollConnection false } - _lastPointersInfo = pointersInfo + lastPointersInfo = pointersInfo val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable) if (canStart) { @@ -718,12 +679,11 @@ internal class NestedScrollHandlerImpl( canStart }, onStart = { firstScroll -> - val pointersInfo = pointersInfo() + val pointersInfo = lastPointersInfo scrollController( dragController = draggableHandler.onDragStarted( - pointersDown = pointersInfo.pointersDown, - startedPosition = pointersInfo.startedPosition, + pointersInfo = pointersInfo, overSlop = if (isIntercepting) 0f else firstScroll, ), canChangeScene = canChangeScene, @@ -742,7 +702,7 @@ private fun scrollController( return object : ScrollController { override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float { val pointersInfo = pointersInfoOwner.pointersInfo() - if (pointersInfo.isMouseWheel) { + if (pointersInfo?.isMouseWheel == true) { // Do not support mouse wheel interactions return 0f } 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/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index 8613f6da0f62..ab2324a87d81 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.PointerId import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope +import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode import androidx.compose.ui.input.pointer.changedToDown import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed @@ -52,6 +53,7 @@ import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastFilter import androidx.compose.ui.util.fastFirstOrNull +import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastSumBy import com.android.compose.ui.util.SpaceVectorConverter import kotlin.coroutines.cancellation.CancellationException @@ -78,8 +80,8 @@ import kotlinx.coroutines.launch @Stable internal fun Modifier.multiPointerDraggable( orientation: Orientation, - startDragImmediately: (startedPosition: Offset) -> Boolean, - onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + startDragImmediately: (pointersInfo: PointersInfo) -> Boolean, + onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController, onFirstPointerDown: () -> Unit = {}, swipeDetector: SwipeDetector = DefaultSwipeDetector, dispatcher: NestedScrollDispatcher, @@ -97,9 +99,8 @@ internal fun Modifier.multiPointerDraggable( private data class MultiPointerDraggableElement( private val orientation: Orientation, - private val startDragImmediately: (startedPosition: Offset) -> Boolean, - private val onDragStarted: - (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + private val startDragImmediately: (pointersInfo: PointersInfo) -> Boolean, + private val onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController, private val onFirstPointerDown: () -> Unit, private val swipeDetector: SwipeDetector, private val dispatcher: NestedScrollDispatcher, @@ -125,9 +126,8 @@ private data class MultiPointerDraggableElement( internal class MultiPointerDraggableNode( orientation: Orientation, - var startDragImmediately: (startedPosition: Offset) -> Boolean, - var onDragStarted: - (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + var startDragImmediately: (pointersInfo: PointersInfo) -> Boolean, + var onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController, var onFirstPointerDown: () -> Unit, swipeDetector: SwipeDetector = DefaultSwipeDetector, private val dispatcher: NestedScrollDispatcher, @@ -183,17 +183,22 @@ internal class MultiPointerDraggableNode( pointerInput.onPointerEvent(pointerEvent, pass, bounds) } + private var lastPointerEvent: PointerEvent? = null private var startedPosition: Offset? = null private var pointersDown: Int = 0 - private var isMouseWheel: Boolean = false - internal fun pointersInfo(): PointersInfo { - return PointersInfo( + internal fun pointersInfo(): PointersInfo? { + val startedPosition = startedPosition + val lastPointerEvent = lastPointerEvent + if (startedPosition == null || lastPointerEvent == null) { // This may be null, i.e. when the user uses TalkBack + return null + } + + return PointersInfo( startedPosition = startedPosition, - // We could have 0 pointers during fling or for other reasons. - pointersDown = pointersDown.coerceAtLeast(1), - isMouseWheel = isMouseWheel, + pointersDown = pointersDown, + lastPointerEvent = lastPointerEvent, ) } @@ -212,8 +217,8 @@ internal class MultiPointerDraggableNode( if (pointerEvent.type == PointerEventType.Enter) continue val changes = pointerEvent.changes + lastPointerEvent = pointerEvent pointersDown = changes.countDown() - isMouseWheel = pointerEvent.type == PointerEventType.Scroll when { // There are no more pointers down. @@ -285,8 +290,8 @@ internal class MultiPointerDraggableNode( detectDragGestures( orientation = orientation, startDragImmediately = startDragImmediately, - onDragStart = { startedPosition, overSlop, pointersDown -> - onDragStarted(startedPosition, overSlop, pointersDown) + onDragStart = { pointersInfo, overSlop -> + onDragStarted(pointersInfo, overSlop) }, onDrag = { controller, amount -> dispatchScrollEvents( @@ -435,9 +440,8 @@ internal class MultiPointerDraggableNode( */ private suspend fun AwaitPointerEventScope.detectDragGestures( orientation: Orientation, - startDragImmediately: (startedPosition: Offset) -> Boolean, - onDragStart: - (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + startDragImmediately: (pointersInfo: PointersInfo) -> Boolean, + onDragStart: (pointersInfo: PointersInfo, overSlop: Float) -> DragController, onDrag: (controller: DragController, dragAmount: Float) -> Unit, onDragEnd: (controller: DragController) -> Unit, onDragCancel: (controller: DragController) -> Unit, @@ -462,8 +466,13 @@ internal class MultiPointerDraggableNode( .first() var overSlop = 0f + var lastPointersInfo = + checkNotNull(pointersInfo()) { + "We should have pointers down, last event: $currentEvent" + } + val drag = - if (startDragImmediately(consumablePointer.position)) { + if (startDragImmediately(lastPointersInfo)) { consumablePointer.consume() consumablePointer } else { @@ -488,14 +497,18 @@ internal class MultiPointerDraggableNode( consumablePointer.id, onSlopReached, ) - } + } ?: return + lastPointersInfo = + checkNotNull(pointersInfo()) { + "We should have pointers down, last event: $currentEvent" + } // Make sure that overSlop is not 0f. This can happen when the user drags by exactly // the touch slop. However, the overSlop we pass to onDragStarted() is used to // compute the direction we are dragging in, so overSlop should never be 0f unless // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned // true). - if (drag != null && overSlop == 0f) { + if (overSlop == 0f) { val delta = (drag.position - consumablePointer.position).toFloat() check(delta != 0f) { "delta is equal to 0" } overSlop = delta.sign @@ -503,49 +516,38 @@ internal class MultiPointerDraggableNode( drag } - if (drag != null) { - val controller = - onDragStart( - // The startedPosition is the starting position when a gesture begins (when the - // first pointer touches the screen), not the point where we begin dragging. - // For example, this could be different if one of our children intercepts the - // gesture first and then we do. - requireNotNull(startedPosition), - overSlop, - pointersDown, + val controller = onDragStart(lastPointersInfo, overSlop) + + val successful: Boolean + try { + onDrag(controller, overSlop) + + successful = + drag( + initialPointerId = drag.id, + hasDragged = { it.positionChangeIgnoreConsumed().toFloat() != 0f }, + onDrag = { + onDrag(controller, it.positionChange().toFloat()) + it.consume() + }, + onIgnoredEvent = { + // We are still dragging an object, but this event is not of interest to the + // caller. + // This event will not trigger the onDrag event, but we will consume the + // event to prevent another pointerInput from interrupting the current + // gesture just because the event was ignored. + it.consume() + }, ) + } catch (t: Throwable) { + onDragCancel(controller) + throw t + } - val successful: Boolean - try { - onDrag(controller, overSlop) - - successful = - drag( - initialPointerId = drag.id, - hasDragged = { it.positionChangeIgnoreConsumed().toFloat() != 0f }, - onDrag = { - onDrag(controller, it.positionChange().toFloat()) - it.consume() - }, - onIgnoredEvent = { - // We are still dragging an object, but this event is not of interest to - // the caller. - // This event will not trigger the onDrag event, but we will consume the - // event to prevent another pointerInput from interrupting the current - // gesture just because the event was ignored. - it.consume() - }, - ) - } catch (t: Throwable) { - onDragCancel(controller) - throw t - } - - if (successful) { - onDragEnd(controller) - } else { - onDragCancel(controller) - } + if (successful) { + onDragEnd(controller) + } else { + onDragCancel(controller) } } @@ -655,11 +657,57 @@ internal class MultiPointerDraggableNode( } internal fun interface PointersInfoOwner { - fun pointersInfo(): PointersInfo + /** + * Provides information about the pointers interacting with this composable. + * + * @return A [PointersInfo] object containing details about the pointers, including the starting + * position and the number of pointers down, or `null` if there are no pointers down. + */ + fun pointersInfo(): PointersInfo? } +/** + * Holds information about pointer interactions within a composable. + * + * This class stores details such as the starting position of a gesture, the number of pointers + * down, and whether the last pointer event was a mouse wheel scroll. + * + * @param startedPosition The starting position of the gesture. This is the position where the first + * pointer touched the screen, not necessarily the point where dragging begins. This may be + * different from the initial touch position if a child composable intercepts the gesture before + * this one. + * @param pointersDown The number of pointers currently down. + * @param isMouseWheel Indicates whether the last pointer event was a mouse wheel scroll. + * @param pointersDownByType Provide a map of pointer types to the count of pointers of that type + * currently down/pressed. + */ internal data class PointersInfo( - val startedPosition: Offset?, + val startedPosition: Offset, val pointersDown: Int, val isMouseWheel: Boolean, -) + val pointersDownByType: Map<PointerType, Int>, +) { + init { + check(pointersDown > 0) { "We should have at least 1 pointer down, $pointersDown instead" } + } +} + +private fun PointersInfo( + startedPosition: Offset, + pointersDown: Int, + lastPointerEvent: PointerEvent, +): PointersInfo { + return PointersInfo( + startedPosition = startedPosition, + pointersDown = pointersDown, + isMouseWheel = lastPointerEvent.type == PointerEventType.Scroll, + pointersDownByType = + buildMap { + lastPointerEvent.changes.fastForEach { change -> + if (!change.pressed) return@fastForEach + val newValue = (get(change.type) ?: 0) + 1 + put(change.type, newValue) + } + }, + ) +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt index 077927dfe0a2..5bf77ae9b23c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt @@ -233,6 +233,12 @@ sealed interface ObservableTransitionState { (to == null || this.toContent == to) } + fun isTransitioningSets(from: Set<ContentKey>? = null, to: Set<ContentKey>? = null): Boolean { + return this is Transition && + (from == null || from.contains(this.fromContent)) && + (to == null || to.contains(this.toContent)) + } + /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */ fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean { return isTransitioning(from = content, to = other) || 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 504240390674..21d87e173728 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 @@ -27,6 +27,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Density @@ -407,6 +408,7 @@ data object Back : UserAction() { data class Swipe( val direction: SwipeDirection, val pointerCount: Int = 1, + val pointersType: PointerType? = null, val fromSource: SwipeSource? = null, ) : UserAction() { companion object { @@ -422,6 +424,7 @@ data class Swipe( return Resolved( direction = direction.resolve(layoutDirection), pointerCount = pointerCount, + pointersType = pointersType, fromSource = fromSource?.resolve(layoutDirection), ) } @@ -431,6 +434,7 @@ data class Swipe( val direction: SwipeDirection.Resolved, val pointerCount: Int, val fromSource: SwipeSource.Resolved?, + val pointersType: PointerType?, ) : 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/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index fdf01cce396b..ba5f4144aff9 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -19,7 +19,6 @@ package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode import androidx.compose.ui.input.pointer.PointerEvent @@ -65,6 +64,52 @@ private fun Content.shouldEnableSwipes(orientation: Orientation): Boolean { return userActions.keys.any { it is Swipe.Resolved && it.direction.orientation == orientation } } +/** + * Finds the best matching [UserActionResult] for the given [swipe] within this [Content]. + * Prioritizes actions with matching [Swipe.Resolved.fromSource]. + * + * @param swipe The swipe to match against. + * @return The best matching [UserActionResult], or `null` if no match is found. + */ +internal fun Content.findActionResultBestMatch(swipe: Swipe.Resolved): UserActionResult? { + var bestPoints = Int.MIN_VALUE + var bestMatch: UserActionResult? = null + userActions.forEach { (actionSwipe, actionResult) -> + if ( + actionSwipe !is Swipe.Resolved || + // The direction must match. + actionSwipe.direction != swipe.direction || + // The number of pointers down must match. + actionSwipe.pointerCount != swipe.pointerCount || + // The action requires a specific fromSource. + (actionSwipe.fromSource != null && actionSwipe.fromSource != swipe.fromSource) || + // The action requires a specific pointerType. + (actionSwipe.pointersType != null && actionSwipe.pointersType != swipe.pointersType) + ) { + // This action is not eligible. + return@forEach + } + + val sameFromSource = actionSwipe.fromSource == swipe.fromSource + val samePointerType = actionSwipe.pointersType == swipe.pointersType + // Prioritize actions with a perfect match. + if (sameFromSource && samePointerType) { + return actionResult + } + + var points = 0 + if (sameFromSource) points++ + if (samePointerType) points++ + + // Otherwise, keep track of the best eligible action. + if (points > bestPoints) { + bestPoints = points + bestMatch = actionResult + } + } + return bestMatch +} + private data class SwipeToSceneElement( val draggableHandler: DraggableHandlerImpl, val swipeDetector: SwipeDetector, @@ -155,10 +200,10 @@ private class SwipeToSceneNode( override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput() - private fun startDragImmediately(startedPosition: Offset): Boolean { + private fun startDragImmediately(pointersInfo: PointersInfo): Boolean { // Immediately start the drag if the user can't swipe in the other direction and the gesture // handler can intercept it. - return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(startedPosition) + return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersInfo) } private fun canOppositeSwipe(): Boolean { 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/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index f24d93f0d79d..5dad0d75cfc5 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 @@ -23,6 +23,7 @@ import androidx.compose.material3.Text import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput +import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection @@ -51,6 +52,20 @@ import org.junit.runner.RunWith private const val SCREEN_SIZE = 100f private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt()) +private fun pointersInfo( + startedPosition: Offset = Offset.Zero, + pointersDown: Int = 1, + isMouseWheel: Boolean = false, + pointersDownByType: Map<PointerType, Int> = mapOf(PointerType.Touch to pointersDown), +): PointersInfo { + return PointersInfo( + startedPosition = startedPosition, + pointersDown = pointersDown, + isMouseWheel = isMouseWheel, + pointersDownByType = pointersDownByType, + ) +} + @RunWith(AndroidJUnit4::class) class DraggableHandlerTest { private class TestGestureScope(val testScope: MonotonicClockTestScope) { @@ -126,9 +141,7 @@ class DraggableHandlerTest { val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical) val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal) - var pointerInfoOwner: () -> PointersInfo = { - PointersInfo(startedPosition = Offset.Zero, pointersDown = 1, isMouseWheel = false) - } + var pointerInfoOwner: () -> PointersInfo = { pointersInfo() } fun nestedScrollConnection( nestedScrollBehavior: NestedScrollBehavior, @@ -211,42 +224,32 @@ class DraggableHandlerTest { } fun onDragStarted( - startedPosition: Offset = Offset.Zero, + pointersInfo: PointersInfo = pointersInfo(), overSlop: Float, - pointersDown: Int = 1, expectedConsumedOverSlop: Float = overSlop, ): DragController { // overSlop should be 0f only if the drag gesture starts with startDragImmediately if (overSlop == 0f) error("Consider using onDragStartedImmediately()") return onDragStarted( draggableHandler = draggableHandler, - startedPosition = startedPosition, + pointersInfo = pointersInfo, overSlop = overSlop, - pointersDown = pointersDown, expectedConsumedOverSlop = expectedConsumedOverSlop, ) } - fun onDragStartedImmediately( - startedPosition: Offset = Offset.Zero, - pointersDown: Int = 1, - ): DragController { - return onDragStarted(draggableHandler, startedPosition, overSlop = 0f, pointersDown) + fun onDragStartedImmediately(pointersInfo: PointersInfo = pointersInfo()): DragController { + return onDragStarted(draggableHandler, pointersInfo, overSlop = 0f) } fun onDragStarted( draggableHandler: DraggableHandler, - startedPosition: Offset = Offset.Zero, + pointersInfo: PointersInfo = pointersInfo(), overSlop: Float = 0f, - pointersDown: Int = 1, expectedConsumedOverSlop: Float = overSlop, ): DragController { val dragController = - draggableHandler.onDragStarted( - startedPosition = startedPosition, - overSlop = overSlop, - pointersDown = pointersDown, - ) + draggableHandler.onDragStarted(pointersInfo = pointersInfo, overSlop = overSlop) // MultiPointerDraggable will always call onDelta with the initial overSlop right after dragController.onDragDelta(pixels = overSlop, expectedConsumedOverSlop) @@ -528,7 +531,8 @@ class DraggableHandlerTest { mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC) val dragController = onDragStarted( - startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f), + pointersInfo = + pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f)), overSlop = up(fractionOfScreen = 0.2f), ) assertTransition( @@ -554,7 +558,7 @@ class DraggableHandlerTest { // Start dragging from the bottom onDragStarted( - startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE), + pointersInfo = pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE)), overSlop = up(fractionOfScreen = 0.1f), ) assertTransition( @@ -1051,8 +1055,8 @@ class DraggableHandlerTest { navigateToSceneC() // Swipe up from the middle to transition to scene B. - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) - onDragStarted(startedPosition = middle, overSlop = up(0.1f)) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + onDragStarted(pointersInfo = middle, overSlop = up(0.1f)) assertTransition( currentScene = SceneC, fromScene = SceneC, @@ -1067,7 +1071,7 @@ class DraggableHandlerTest { // should intercept it. Because it is intercepted, the overSlop passed to onDragStarted() // should be 0f. assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue() - onDragStartedImmediately(startedPosition = middle) + onDragStartedImmediately(pointersInfo = middle) // We should have intercepted the transition, so the transition should be the same object. assertTransition( @@ -1083,9 +1087,9 @@ class DraggableHandlerTest { // Start a new gesture from the bottom of the screen. Because swiping up from the bottom of // C leads to scene A (and not B), the previous transitions is *not* intercepted and we // instead animate from C to A. - val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE) + val bottom = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)) assertThat(draggableHandler.shouldImmediatelyIntercept(bottom)).isFalse() - onDragStarted(startedPosition = bottom, overSlop = up(0.1f)) + onDragStarted(pointersInfo = bottom, overSlop = up(0.1f)) assertTransition( currentScene = SceneC, @@ -1102,8 +1106,8 @@ class DraggableHandlerTest { navigateToSceneC() // Swipe up from the middle to transition to scene B. - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) - onDragStarted(startedPosition = middle, overSlop = up(0.1f)) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + onDragStarted(pointersInfo = middle, overSlop = up(0.1f)) assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true) // The current transition can be intercepted. @@ -1119,15 +1123,15 @@ class DraggableHandlerTest { @Test fun interruptedTransitionCanNotBeImmediatelyIntercepted() = runGestureTest { - assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse() + assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse() onDragStarted(overSlop = up(0.1f)) - assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue() + assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue() layoutState.startTransitionImmediately( animationScope = testScope.backgroundScope, transition(SceneA, SceneB), ) - assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse() + assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse() } @Test @@ -1159,7 +1163,7 @@ class DraggableHandlerTest { assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB) // Intercept the transition and swipe down back to scene A. - assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue() + assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue() val dragController2 = onDragStartedImmediately() // Block the transition when the user release their finger. @@ -1203,9 +1207,7 @@ class DraggableHandlerTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) // Drag from the **top** of the screen - pointerInfoOwner = { - PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = false) - } + pointerInfoOwner = { pointersInfo() } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) @@ -1222,13 +1224,7 @@ class DraggableHandlerTest { advanceUntilIdle() // Drag from the **bottom** of the screen - pointerInfoOwner = { - PointersInfo( - startedPosition = Offset(0f, SCREEN_SIZE), - pointersDown = 1, - isMouseWheel = false, - ) - } + pointerInfoOwner = { pointersInfo(startedPosition = Offset(0f, SCREEN_SIZE)) } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) @@ -1248,9 +1244,7 @@ class DraggableHandlerTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) // Use mouse wheel - pointerInfoOwner = { - PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = true) - } + pointerInfoOwner = { pointersInfo(isMouseWheel = true) } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) @@ -1260,8 +1254,8 @@ class DraggableHandlerTest { @Test fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest { // Swipe up from the middle to transition to scene B. - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) - val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.1f)) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.1f)) assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true) dragController.onDragStoppedAnimateLater(velocity = 0f) @@ -1274,10 +1268,10 @@ class DraggableHandlerTest { layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) } // Swipe up to scene B at progress = 200%. - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) val dragController = onDragStarted( - startedPosition = middle, + pointersInfo = middle, overSlop = up(2f), // Overscroll is disabled, it will scroll up to 100% expectedConsumedOverSlop = up(1f), @@ -1305,8 +1299,8 @@ class DraggableHandlerTest { layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) } // Swipe up to scene B at progress = 200%. - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) - val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.99f)) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.99f)) assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.99f) // Release the finger. @@ -1351,9 +1345,9 @@ class DraggableHandlerTest { overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) } } - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) - val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.5f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.5f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneB) @@ -1383,9 +1377,9 @@ class DraggableHandlerTest { overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) } } - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) - val dragController = onDragStarted(startedPosition = middle, overSlop = down(0.5f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = down(0.5f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneC) @@ -1414,9 +1408,9 @@ class DraggableHandlerTest { overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) } } - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) - val dragController = onDragStarted(startedPosition = middle, overSlop = up(1.5f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1.5f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneB) @@ -1446,9 +1440,9 @@ class DraggableHandlerTest { overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) } } - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) - val dragController = onDragStarted(startedPosition = middle, overSlop = down(1.5f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1.5f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneC) @@ -1480,8 +1474,8 @@ class DraggableHandlerTest { mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB)) - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) - val dragController = onDragStarted(startedPosition = middle, overSlop = down(1f)) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneB) @@ -1513,8 +1507,8 @@ class DraggableHandlerTest { mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC)) - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) - val dragController = onDragStarted(startedPosition = middle, overSlop = up(1f)) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneC) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt index 3df608717414..5ec74f8d2260 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt @@ -98,7 +98,7 @@ class MultiPointerDraggableTest { Modifier.multiPointerDraggable( orientation = Orientation.Vertical, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> started = true SimpleDragController( onDrag = { dragged = true }, @@ -167,7 +167,7 @@ class MultiPointerDraggableTest { orientation = Orientation.Vertical, // We want to start a drag gesture immediately startDragImmediately = { true }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> started = true SimpleDragController( onDrag = { dragged = true }, @@ -239,7 +239,7 @@ class MultiPointerDraggableTest { .multiPointerDraggable( orientation = Orientation.Vertical, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> started = true SimpleDragController( onDrag = { dragged = true }, @@ -358,7 +358,7 @@ class MultiPointerDraggableTest { .multiPointerDraggable( orientation = Orientation.Vertical, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> started = true SimpleDragController( onDrag = { dragged = true }, @@ -463,7 +463,7 @@ class MultiPointerDraggableTest { .multiPointerDraggable( orientation = Orientation.Vertical, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> verticalStarted = true SimpleDragController( onDrag = { verticalDragged = true }, @@ -475,7 +475,7 @@ class MultiPointerDraggableTest { .multiPointerDraggable( orientation = Orientation.Horizontal, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> horizontalStarted = true SimpleDragController( onDrag = { horizontalDragged = true }, @@ -574,7 +574,7 @@ class MultiPointerDraggableTest { return swipeConsume } }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> started = true SimpleDragController( onDrag = { /* do nothing */ }, @@ -668,7 +668,7 @@ class MultiPointerDraggableTest { .multiPointerDraggable( orientation = Orientation.Vertical, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> SimpleDragController( onDrag = { consumedOnDrag = it }, onStop = { consumedOnDragStop = it }, @@ -739,7 +739,7 @@ class MultiPointerDraggableTest { .multiPointerDraggable( orientation = Orientation.Vertical, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> SimpleDragController( onDrag = { /* do nothing */ }, onStop = { 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 2bc9b3826548..aaeaba93304d 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 @@ -38,6 +38,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.testTag @@ -61,6 +62,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC +import com.android.compose.animation.scene.TestScenes.SceneD import com.android.compose.animation.scene.subjects.assertThat import com.google.common.truth.Truth.assertThat import org.junit.Rule @@ -127,6 +129,7 @@ class SwipeToSceneTest { 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, ) @@ -134,6 +137,12 @@ class SwipeToSceneTest { ) { Box(Modifier.fillMaxSize()) } + scene( + key = SceneD, + userActions = if (swipesEnabled()) mapOf(Swipe.Up to SceneC) else emptyMap(), + ) { + Box(Modifier.fillMaxSize()) + } } } @@ -502,6 +511,45 @@ class SwipeToSceneTest { } @Test + fun mousePointerSwipe() { + // Start at scene C. + val layoutState = layoutState(SceneC) + + // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is + // detected as a drag event. + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + TestContent(layoutState) + } + + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) + + rule.onRoot().performMouseInput { + enter(middle) + press() + moveBy(Offset(0f, touchSlop + 10.dp.toPx()), 1_000) + } + + // We are transitioning to D because we are moving the mouse while the primary button is + // pressed. + val transition = assertThat(layoutState.transitionState).isSceneTransition() + assertThat(transition).hasFromScene(SceneC) + assertThat(transition).hasToScene(SceneD) + + rule.onRoot().performMouseInput { + release() + exit(middle) + } + // Release the mouse primary button and wait for the animation to end. We are back to C + // because we only swiped 10dp. + rule.waitForIdle() + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) + } + + @Test fun mouseWheel_pointerInputApi_ignoredByStl() { val layoutState = layoutState() var touchSlop = 0f 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/utils/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt index f39dd676fb6e..95ef2ce821e1 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt @@ -65,4 +65,6 @@ val EmptyTestTransitions = transitions { } from(TestScenes.SceneC, to = TestScenes.SceneA) { spec = snap() } + + from(TestScenes.SceneC, to = TestScenes.SceneD) { spec = snap() } } 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/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt index 2a87452b0b6a..ae18aac66215 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt @@ -16,102 +16,19 @@ package com.android.systemui.shared.settings.data.repository -import android.content.ContentResolver -import android.database.ContentObserver import android.provider.Settings -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.withContext -/** - * Defines interface for classes that can provide access to data from [Settings.Secure]. - * This repository doesn't guarantee to provide value across different users. For that - * see: [UserAwareSecureSettingsRepository] - */ +/** Defines interface for classes that can provide access to data from [Settings.Secure]. */ interface SecureSettingsRepository { /** Returns a [Flow] tracking the value of a setting as an [Int]. */ - fun intSetting( - name: String, - defaultValue: Int = 0, - ): Flow<Int> + fun intSetting(name: String, defaultValue: Int = 0): Flow<Int> /** Updates the value of the setting with the given name. */ - suspend fun setInt( - name: String, - value: Int, - ) + suspend fun setInt(name: String, value: Int) - suspend fun getInt( - name: String, - defaultValue: Int = 0, - ): Int + suspend fun getInt(name: String, defaultValue: Int = 0): Int suspend fun getString(name: String): String? } - -class SecureSettingsRepositoryImpl( - private val contentResolver: ContentResolver, - private val backgroundDispatcher: CoroutineDispatcher, -) : SecureSettingsRepository { - - override fun intSetting( - name: String, - defaultValue: Int, - ): Flow<Int> { - return callbackFlow { - val observer = - object : ContentObserver(null) { - override fun onChange(selfChange: Boolean) { - trySend(Unit) - } - } - - contentResolver.registerContentObserver( - Settings.Secure.getUriFor(name), - /* notifyForDescendants= */ false, - observer, - ) - send(Unit) - - awaitClose { contentResolver.unregisterContentObserver(observer) } - } - .map { Settings.Secure.getInt(contentResolver, name, defaultValue) } - // The above work is done on the background thread (which is important for accessing - // settings through the content resolver). - .flowOn(backgroundDispatcher) - } - - override suspend fun setInt(name: String, value: Int) { - withContext(backgroundDispatcher) { - Settings.Secure.putInt( - contentResolver, - name, - value, - ) - } - } - - override suspend fun getInt(name: String, defaultValue: Int): Int { - return withContext(backgroundDispatcher) { - Settings.Secure.getInt( - contentResolver, - name, - defaultValue, - ) - } - } - - override suspend fun getString(name: String): String? { - return withContext(backgroundDispatcher) { - Settings.Secure.getString( - contentResolver, - name, - ) - } - } -} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt new file mode 100644 index 000000000000..8b9fcb496f59 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.settings.data.repository + +import android.content.ContentResolver +import android.database.ContentObserver +import android.provider.Settings +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext + +/** + * Simple implementation of [SecureSettingsRepository]. + * + * This repository doesn't guarantee to provide value across different users, and therefore + * shouldn't be used in SystemUI. For that see: [UserAwareSecureSettingsRepository] + */ +// TODO: b/377244768 - Move to Theme/WallpaperPicker, away from SystemUI. +class SecureSettingsRepositoryImpl( + private val contentResolver: ContentResolver, + private val backgroundDispatcher: CoroutineDispatcher, +) : SecureSettingsRepository { + + override fun intSetting(name: String, defaultValue: Int): Flow<Int> { + return callbackFlow { + val observer = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(Unit) + } + } + + contentResolver.registerContentObserver( + Settings.Secure.getUriFor(name), + /* notifyForDescendants= */ false, + observer, + ) + send(Unit) + + awaitClose { contentResolver.unregisterContentObserver(observer) } + } + .map { Settings.Secure.getInt(contentResolver, name, defaultValue) } + // The above work is done on the background thread (which is important for accessing + // settings through the content resolver). + .flowOn(backgroundDispatcher) + } + + override suspend fun setInt(name: String, value: Int) { + withContext(backgroundDispatcher) { Settings.Secure.putInt(contentResolver, name, value) } + } + + override suspend fun getInt(name: String, defaultValue: Int): Int { + return withContext(backgroundDispatcher) { + Settings.Secure.getInt(contentResolver, name, defaultValue) + } + } + + override suspend fun getString(name: String): String? { + return withContext(backgroundDispatcher) { + Settings.Secure.getString(contentResolver, name) + } + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt index afe82fb2a5fa..8cda9b3d9985 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt @@ -16,102 +16,19 @@ package com.android.systemui.shared.settings.data.repository -import android.content.ContentResolver -import android.database.ContentObserver import android.provider.Settings -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.withContext -/** - * Defines interface for classes that can provide access to data from [Settings.System]. This - * repository doesn't guarantee to provide value across different users. For that see: - * [UserAwareSecureSettingsRepository] which does that for secure settings. - */ +/** Interface for classes that can provide access to data from [Settings.System]. */ interface SystemSettingsRepository { /** Returns a [Flow] tracking the value of a setting as an [Int]. */ - fun intSetting( - name: String, - defaultValue: Int = 0, - ): Flow<Int> + fun intSetting(name: String, defaultValue: Int = 0): Flow<Int> /** Updates the value of the setting with the given name. */ - suspend fun setInt( - name: String, - value: Int, - ) + suspend fun setInt(name: String, value: Int) - suspend fun getInt( - name: String, - defaultValue: Int = 0, - ): Int + suspend fun getInt(name: String, defaultValue: Int = 0): Int suspend fun getString(name: String): String? } - -class SystemSettingsRepositoryImpl( - private val contentResolver: ContentResolver, - private val backgroundDispatcher: CoroutineDispatcher, -) : SystemSettingsRepository { - - override fun intSetting( - name: String, - defaultValue: Int, - ): Flow<Int> { - return callbackFlow { - val observer = - object : ContentObserver(null) { - override fun onChange(selfChange: Boolean) { - trySend(Unit) - } - } - - contentResolver.registerContentObserver( - Settings.System.getUriFor(name), - /* notifyForDescendants= */ false, - observer, - ) - send(Unit) - - awaitClose { contentResolver.unregisterContentObserver(observer) } - } - .map { Settings.System.getInt(contentResolver, name, defaultValue) } - // The above work is done on the background thread (which is important for accessing - // settings through the content resolver). - .flowOn(backgroundDispatcher) - } - - override suspend fun setInt(name: String, value: Int) { - withContext(backgroundDispatcher) { - Settings.System.putInt( - contentResolver, - name, - value, - ) - } - } - - override suspend fun getInt(name: String, defaultValue: Int): Int { - return withContext(backgroundDispatcher) { - Settings.System.getInt( - contentResolver, - name, - defaultValue, - ) - } - } - - override suspend fun getString(name: String): String? { - return withContext(backgroundDispatcher) { - Settings.System.getString( - contentResolver, - name, - ) - } - } -} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt new file mode 100644 index 000000000000..b039a320b987 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.settings.data.repository + +import android.content.ContentResolver +import android.database.ContentObserver +import android.provider.Settings +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext + +/** + * Defines interface for classes that can provide access to data from [Settings.System]. + * + * This repository doesn't guarantee to provide value across different users. For that see: + * [UserAwareSystemSettingsRepository]. + */ +// TODO: b/377244768 - Move to Theme/WallpaperPicker, away from SystemUI. +class SystemSettingsRepositoryImpl( + private val contentResolver: ContentResolver, + private val backgroundDispatcher: CoroutineDispatcher, +) : SystemSettingsRepository { + + override fun intSetting(name: String, defaultValue: Int): Flow<Int> { + return callbackFlow { + val observer = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(Unit) + } + } + + contentResolver.registerContentObserver( + Settings.System.getUriFor(name), + /* notifyForDescendants= */ false, + observer, + ) + send(Unit) + + awaitClose { contentResolver.unregisterContentObserver(observer) } + } + .map { Settings.System.getInt(contentResolver, name, defaultValue) } + // The above work is done on the background thread (which is important for accessing + // settings through the content resolver). + .flowOn(backgroundDispatcher) + } + + override suspend fun setInt(name: String, value: Int) { + withContext(backgroundDispatcher) { Settings.System.putInt(contentResolver, name, value) } + } + + override suspend fun getInt(name: String, defaultValue: Int): Int { + return withContext(backgroundDispatcher) { + Settings.System.getInt(contentResolver, name, defaultValue) + } + } + + override suspend fun getString(name: String): String? { + return withContext(backgroundDispatcher) { + Settings.System.getString(contentResolver, name) + } + } +} 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/OnTeardownRuleTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt new file mode 100644 index 000000000000..8635bb0e8ab2 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt @@ -0,0 +1,124 @@ +/* + * 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 + +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.JUnitCore + +@Suppress("JUnitMalformedDeclaration") +@SmallTest +class OnTeardownRuleTest : SysuiTestCase() { + // None of these inner classes should be run except as part of this utilities-testing test + class HasTeardown { + @get:Rule val teardownRule = OnTeardownRule() + + @Before + fun setUp() { + teardownWasRun = false + teardownRule.onTeardown { teardownWasRun = true } + } + + @Test fun doTest() {} + + companion object { + var teardownWasRun = false + } + } + + @Test + fun teardownRuns() { + val result = JUnitCore().run(HasTeardown::class.java) + assertThat(result.failures).isEmpty() + assertThat(HasTeardown.teardownWasRun).isTrue() + } + + class FirstTeardownFails { + @get:Rule val teardownRule = OnTeardownRule() + + @Before + fun setUp() { + teardownWasRun = false + teardownRule.onTeardown { fail("One fails") } + teardownRule.onTeardown { teardownWasRun = true } + } + + @Test fun doTest() {} + + companion object { + var teardownWasRun = false + } + } + + @Test + fun allTeardownsRun() { + val result = JUnitCore().run(FirstTeardownFails::class.java) + assertThat(result.failures.map { it.message }).isEqualTo(listOf("One fails")) + assertThat(FirstTeardownFails.teardownWasRun).isTrue() + } + + class ThreeTeardowns { + @get:Rule val teardownRule = OnTeardownRule() + + @Before + fun setUp() { + messages.clear() + } + + @Test + fun doTest() { + teardownRule.onTeardown { messages.add("A") } + teardownRule.onTeardown { messages.add("B") } + teardownRule.onTeardown { messages.add("C") } + } + + companion object { + val messages = mutableListOf<String>() + } + } + + @Test + fun reverseOrder() { + val result = JUnitCore().run(ThreeTeardowns::class.java) + assertThat(result.failures).isEmpty() + assertThat(ThreeTeardowns.messages).isEqualTo(listOf("C", "B", "A")) + } + + class TryToDoABadThing { + @get:Rule val teardownRule = OnTeardownRule() + + @Test + fun doTest() { + teardownRule.onTeardown { + teardownRule.onTeardown { + // do nothing + } + } + } + } + + @Test + fun prohibitTeardownDuringTeardown() { + val result = JUnitCore().run(TryToDoABadThing::class.java) + assertThat(result.failures.map { it.message }) + .isEqualTo(listOf("Cannot add new teardown routines after test complete.")) + } +} 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/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt index 176c3ac43936..2594472a9c8a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt @@ -22,12 +22,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule import org.junit.Test @@ -40,8 +41,6 @@ import org.mockito.junit.MockitoRule @RunWith(AndroidJUnit4::class) class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() { private val kosmos = testKosmos() - private val testDispatcher = kosmos.testDispatcher - private val testScope = kosmos.testScope private val secureSettings = kosmos.fakeSettings @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule() @@ -55,8 +54,8 @@ class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() { return UserA11yQsShortcutsRepository( userId, secureSettings, - testScope.backgroundScope, - testDispatcher, + kosmos.testScope.backgroundScope, + kosmos.testDispatcher, ) } } @@ -69,13 +68,13 @@ class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() { AccessibilityQsShortcutsRepositoryImpl( a11yManager, userA11yQsShortcutsRepositoryFactory, - testDispatcher + kosmos.testDispatcher, ) } @Test fun a11yQsShortcutTargetsForCorrectUsers() = - testScope.runTest { + kosmos.runTest { val user0 = 0 val targetsForUser0 = setOf("a", "b", "c") val user1 = 1 @@ -94,7 +93,7 @@ class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() { secureSettings.putStringForUser( SETTING_NAME, a11yQsTargets.joinToString(separator = ":"), - forUser + forUser, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt index 8c7cd619a158..cdda9ccc9b9e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt @@ -47,8 +47,7 @@ import com.android.systemui.shade.ShadeController import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository -import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.util.concurrency.FakeExecutor @@ -61,7 +60,6 @@ import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.runCurrent import org.junit.Before import org.junit.Rule @@ -95,9 +93,6 @@ class BackActionInteractorTest : SysuiTestCase() { @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher @Mock private lateinit var iStatusBarService: IStatusBarService @Mock private lateinit var headsUpManager: HeadsUpManager - private val activeNotificationsRepository = ActiveNotificationListRepository() - private val activeNotificationsInteractor = - ActiveNotificationsInteractor(activeNotificationsRepository, StandardTestDispatcher()) private val keyguardRepository = FakeKeyguardRepository() private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy { @@ -107,7 +102,7 @@ class BackActionInteractorTest : SysuiTestCase() { keyguardRepository, headsUpManager, powerInteractor, - activeNotificationsInteractor, + kosmos.activeNotificationsInteractor, kosmos::sceneInteractor, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt index 44ce08514dee..c3c958ca0e94 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt @@ -20,6 +20,8 @@ import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetProviderInfo import android.content.ComponentName import android.content.Intent +import android.content.IntentSender +import android.os.Binder import android.os.UserHandle import android.testing.TestableLooper import android.widget.RemoteViews @@ -29,6 +31,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper +import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope @@ -43,11 +46,13 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -164,7 +169,7 @@ class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() { } @Test - fun addWidget_getWidgetUpdate() = + fun addWidget_noConfigurationCallback_getWidgetUpdate() = testScope.runTest { setupWidgets() @@ -180,7 +185,7 @@ class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() { assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue() // Add a widget - service.addWidget(ComponentName("pkg_4", "cls_4"), UserHandle.of(0), 3) + service.addWidget(ComponentName("pkg_4", "cls_4"), UserHandle.of(0), 3, null) runCurrent() // Verify an update pushed with widget 4 added @@ -192,6 +197,71 @@ class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() { } @Test + fun addWidget_withConfigurationCallback_configurationFails_doNotAddWidget() = + testScope.runTest { + setupWidgets() + + // Bind service + val binder = underTest.onBind(Intent()) + val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder) + + // Verify the update is as expected + val widgets by collectLastValue(service.listenForWidgetUpdates()) + assertThat(widgets).hasSize(3) + assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue() + assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue() + assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue() + + // Add a widget with a configuration callback that fails + service.addWidget( + ComponentName("pkg_4", "cls_4"), + UserHandle.of(0), + 3, + createConfigureWidgetCallback(success = false), + ) + runCurrent() + + // Verify that widget 4 is not added + assertThat(widgets).hasSize(3) + assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue() + assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue() + assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue() + } + + @Test + fun addWidget_withConfigurationCallback_configurationSucceeds_addWidget() = + testScope.runTest { + setupWidgets() + + // Bind service + val binder = underTest.onBind(Intent()) + val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder) + + // Verify the update is as expected + val widgets by collectLastValue(service.listenForWidgetUpdates()) + assertThat(widgets).hasSize(3) + assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue() + assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue() + assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue() + + // Add a widget with a configuration callback that fails + service.addWidget( + ComponentName("pkg_4", "cls_4"), + UserHandle.of(0), + 3, + createConfigureWidgetCallback(success = true), + ) + runCurrent() + + // Verify that widget 4 is added + assertThat(widgets).hasSize(4) + assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue() + assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue() + assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue() + assertThat(widgets?.get(3)?.has(4, "pkg_4/cls_4", 3, 3)).isTrue() + } + + @Test fun deleteWidget_getWidgetUpdate() = testScope.runTest { setupWidgets() @@ -271,6 +341,21 @@ class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() { assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue() } + @Test + fun getIntentSenderForConfigureActivity() = + testScope.runTest { + val expected = IntentSender(Binder()) + whenever(appWidgetHost.getIntentSenderForConfigureActivity(anyInt(), anyInt())) + .thenReturn(expected) + + // Bind service + val binder = underTest.onBind(Intent()) + val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder) + + val actual = service.getIntentSenderForConfigureActivity(1) + assertThat(actual).isEqualTo(expected) + } + private fun setupWidgets() { widgetRepository.addWidget( appWidgetId = 1, @@ -293,7 +378,7 @@ class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() { } private fun IGlanceableHubWidgetManagerService.listenForWidgetUpdates() = - conflatedCallbackFlow<List<CommunalWidgetContentModel>> { + conflatedCallbackFlow { val listener = object : IGlanceableHubWidgetsListener.Stub() { override fun onWidgetsUpdated(widgets: List<CommunalWidgetContentModel>) { @@ -316,4 +401,15 @@ class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() { this.rank == rank && this.spanY == spanY } + + private fun createConfigureWidgetCallback(success: Boolean): IConfigureWidgetCallback { + return object : IConfigureWidgetCallback.Stub() { + override fun onConfigureWidget( + appWidgetId: Int, + resultReceiver: IConfigureWidgetCallback.IResultReceiver?, + ) { + resultReceiver?.onResult(success) + } + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt index 5d4eaf07be25..e1bdf1c42c9a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt @@ -18,17 +18,18 @@ package com.android.systemui.communal.widgets import android.app.Activity import android.content.ActivityNotFoundException +import android.content.IntentSender +import android.os.Binder +import android.os.OutcomeReceiver import androidx.activity.ComponentActivity import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper +import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async @@ -38,15 +39,22 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Mock -import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class WidgetConfigurationControllerTest : SysuiTestCase() { - @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost - @Mock private lateinit var ownerActivity: ComponentActivity + private val appWidgetHost = mock<CommunalAppWidgetHost>() + private val ownerActivity = mock<ComponentActivity>() + + private val outcomeReceiverCaptor = argumentCaptor<OutcomeReceiver<IntentSender?, Throwable>>() private val kosmos = testKosmos() @@ -54,18 +62,19 @@ class WidgetConfigurationControllerTest : SysuiTestCase() { @Before fun setUp() { - MockitoAnnotations.initMocks(this) underTest = WidgetConfigurationController( ownerActivity, { appWidgetHost }, kosmos.testDispatcher, kosmos.fakeGlanceableHubMultiUserHelper, + { kosmos.mockGlanceableHubWidgetManager }, + kosmos.fakeExecutor, ) } @Test - fun configurationFailsWhenActivityNotFound() = + fun configureWidget_activityNotFound_returnsFalse() = with(kosmos) { testScope.runTest { whenever( @@ -84,13 +93,97 @@ class WidgetConfigurationControllerTest : SysuiTestCase() { } @Test - fun configurationFails() = + fun configureWidget_configurationFails_returnsFalse() = + with(kosmos) { + testScope.runTest { + val result = async { underTest.configureWidget(123) } + runCurrent() + assertThat(result.isCompleted).isFalse() + + underTest.setConfigurationResult(Activity.RESULT_CANCELED) + runCurrent() + + assertThat(result.await()).isFalse() + result.cancel() + } + } + + @Test + fun configureWidget_configurationSucceeds_returnsTrue() = + with(kosmos) { + testScope.runTest { + val result = async { underTest.configureWidget(123) } + runCurrent() + assertThat(result.isCompleted).isFalse() + + underTest.setConfigurationResult(Activity.RESULT_OK) + runCurrent() + + assertThat(result.await()).isTrue() + result.cancel() + } + } + + @Test + fun configureWidget_headlessSystemUser_activityNotFound_returnsFalse() = + with(kosmos) { + testScope.runTest { + fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true) + + // Activity not found + whenever( + mockGlanceableHubWidgetManager.getIntentSenderForConfigureActivity( + anyInt(), + outcomeReceiverCaptor.capture(), + any(), + ) + ) + .then { outcomeReceiverCaptor.firstValue.onError(ActivityNotFoundException()) } + + val result = async { underTest.configureWidget(123) } + runCurrent() + + assertThat(result.await()).isFalse() + result.cancel() + } + } + + @Test + fun configureWidget_headlessSystemUser_intentSenderNull_returnsFalse() = + with(kosmos) { + testScope.runTest { + fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true) + + prepareIntentSender(null) + + assertThat(underTest.configureWidget(123)).isFalse() + } + } + + @Test + fun configureWidget_headlessSystemUser_configurationFails_returnsFalse() = with(kosmos) { testScope.runTest { + fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true) + + val intentSender = IntentSender(Binder()) + prepareIntentSender(intentSender) + val result = async { underTest.configureWidget(123) } runCurrent() assertThat(result.isCompleted).isFalse() + verify(ownerActivity) + .startIntentSenderForResult( + eq(intentSender), + eq(WidgetConfigurationController.REQUEST_CODE), + anyOrNull(), + anyInt(), + anyInt(), + anyInt(), + any(), + ) + underTest.setConfigurationResult(Activity.RESULT_CANCELED) runCurrent() @@ -100,13 +193,29 @@ class WidgetConfigurationControllerTest : SysuiTestCase() { } @Test - fun configurationSuccessful() = + fun configureWidget_headlessSystemUser_configurationSucceeds_returnsTrue() = with(kosmos) { testScope.runTest { + fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true) + + val intentSender = IntentSender(Binder()) + prepareIntentSender(intentSender) + val result = async { underTest.configureWidget(123) } runCurrent() assertThat(result.isCompleted).isFalse() + verify(ownerActivity) + .startIntentSenderForResult( + eq(intentSender), + eq(WidgetConfigurationController.REQUEST_CODE), + anyOrNull(), + anyInt(), + anyInt(), + anyInt(), + any(), + ) + underTest.setConfigurationResult(Activity.RESULT_OK) runCurrent() @@ -114,4 +223,16 @@ class WidgetConfigurationControllerTest : SysuiTestCase() { result.cancel() } } + + private fun prepareIntentSender(intentSender: IntentSender?) = + with(kosmos) { + whenever( + mockGlanceableHubWidgetManager.getIntentSenderForConfigureActivity( + anyInt(), + outcomeReceiverCaptor.capture(), + any(), + ) + ) + .then { outcomeReceiverCaptor.firstValue.onResult(intentSender) } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt index f8a45e82c2ab..b343def58e29 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt @@ -31,7 +31,8 @@ import com.android.systemui.controls.panels.authorizedPanelsRepository import com.android.systemui.controls.panels.selectedComponentRepository import com.android.systemui.controls.settings.FakeControlsSettingsRepository import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener +import com.android.systemui.dreams.homecontrols.shared.controlsSettings +import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsComponent import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsListingController import com.android.systemui.dreams.homecontrols.system.domain.interactor.homeControlsComponentInteractor @@ -42,13 +43,10 @@ import com.android.systemui.log.logcatLogBuffer import com.android.systemui.settings.fakeUserTracker import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor -import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -56,6 +54,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -90,13 +89,13 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() { fun testRegisterSingleListener() = testScope.runTest { setup() - val controlsSettings by collectLastValue(addCallback()) + val controlsSettings by collectLastValue(underTest.controlsSettings) runServicesUpdate() assertThat(controlsSettings) .isEqualTo( - CallbackArgs( - panelComponent = TEST_COMPONENT, + HomeControlsComponentInfo( + componentName = TEST_COMPONENT, allowTrivialControlsOnLockscreen = false, ) ) @@ -106,21 +105,21 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() { fun testRegisterMultipleListeners() = testScope.runTest { setup() - val controlsSettings1 by collectLastValue(addCallback()) - val controlsSettings2 by collectLastValue(addCallback()) + val controlsSettings1 by collectLastValue(underTest.controlsSettings) + val controlsSettings2 by collectLastValue(underTest.controlsSettings) runServicesUpdate() assertThat(controlsSettings1) .isEqualTo( - CallbackArgs( - panelComponent = TEST_COMPONENT, + HomeControlsComponentInfo( + componentName = TEST_COMPONENT, allowTrivialControlsOnLockscreen = false, ) ) assertThat(controlsSettings2) .isEqualTo( - CallbackArgs( - panelComponent = TEST_COMPONENT, + HomeControlsComponentInfo( + componentName = TEST_COMPONENT, allowTrivialControlsOnLockscreen = false, ) ) @@ -130,13 +129,13 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() { fun testListenerCalledWhenStateChanges() = testScope.runTest { setup() - val controlsSettings by collectLastValue(addCallback()) + val controlsSettings by collectLastValue(underTest.controlsSettings) runServicesUpdate() assertThat(controlsSettings) .isEqualTo( - CallbackArgs( - panelComponent = TEST_COMPONENT, + HomeControlsComponentInfo( + componentName = TEST_COMPONENT, allowTrivialControlsOnLockscreen = false, ) ) @@ -146,13 +145,47 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() { // Updated with null component now that we are no longer authorized. assertThat(controlsSettings) .isEqualTo( - CallbackArgs(panelComponent = null, allowTrivialControlsOnLockscreen = false) + HomeControlsComponentInfo( + componentName = null, + allowTrivialControlsOnLockscreen = false, + ) ) } + @Test + fun testDestroy() = + testScope.runTest { + setup() + val controlsSettings1 by collectLastValue(underTest.controlsSettings) + + assertThat(controlsSettings1) + .isEqualTo( + HomeControlsComponentInfo( + componentName = null, + allowTrivialControlsOnLockscreen = false, + ) + ) + + underTest.onDestroy() + runServicesUpdate() + fakeControlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true) + + // Existing callback is not triggered if destroyed. + assertThat(controlsSettings1) + .isEqualTo( + HomeControlsComponentInfo( + componentName = null, + allowTrivialControlsOnLockscreen = false, + ) + ) + // New callbacks cannot be added. + val controlsSettings2 by collectLastValue(underTest.controlsSettings) + assertThat(controlsSettings2).isNull() + } + private fun TestScope.runServicesUpdate() { runCurrent() - val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true)) + val listings = listOf(buildControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true)) val callback = withArgCaptor { Mockito.verify(kosmos.controlsListingController).addCallback(capture()) } @@ -160,20 +193,6 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() { runCurrent() } - private fun addCallback() = conflatedCallbackFlow { - val callback = - object : IOnControlsSettingsChangeListener.Stub() { - override fun onControlsSettingsChanged( - panelComponent: ComponentName?, - allowTrivialControlsOnLockscreen: Boolean, - ) { - trySend(CallbackArgs(panelComponent, allowTrivialControlsOnLockscreen)) - } - } - underTest.registerListenerForCurrentUser(callback) - awaitClose { underTest.unregisterListenerForCurrentUser(callback) } - } - private suspend fun TestScope.setup() { kosmos.fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) kosmos.fakeUserTracker.set(listOf(PRIMARY_USER), 0) @@ -182,12 +201,7 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() { runCurrent() } - private data class CallbackArgs( - val panelComponent: ComponentName?, - val allowTrivialControlsOnLockscreen: Boolean, - ) - - private fun ControlsServiceInfo( + private fun buildControlsServiceInfo( componentName: ComponentName, label: CharSequence, hasPanel: Boolean, @@ -225,7 +239,7 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() { UserInfo( /* id= */ PRIMARY_USER_ID, /* name= */ "primary user", - /* flags= */ UserInfo.FLAG_PRIMARY, + /* flags= */ UserInfo.FLAG_MAIN, ) private const val TEST_PACKAGE = "pkg" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt index f331060db43e..5827c7b444d7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt @@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogBuffer import com.android.systemui.log.table.TableLogBuffer import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test @@ -134,6 +135,21 @@ class DumpManagerTest : SysuiTestCase() { } @Test + fun registerDumpable_supportsAnonymousDumpables() { + val anonDumpable = + object : Dumpable { + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("AnonDumpable") + } + } + + // THEN registration with implicit names should succeed + dumpManager.registerCriticalDumpable(anonDumpable) + + // No exception thrown + } + + @Test fun getDumpables_returnsSafeCollection() { // GIVEN a variety of registered dumpables dumpManager.registerCriticalDumpable("dumpable1", dumpable1) 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/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt index 26ce67da10b3..7ec53dfbdd10 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt @@ -40,8 +40,8 @@ import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock +import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository import com.android.systemui.util.settings.fakeSettings -import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -72,14 +72,13 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { @Before fun setup() { - val settingsRepository = - UserAwareSecureSettingsRepositoryImpl(secureSettings, userRepository, dispatcher) + val settingsRepository = kosmos.userAwareSecureSettingsRepository val stickyKeysRepository = StickyKeysRepositoryImpl( inputManager, dispatcher, settingsRepository, - mock<StickyKeysLogger>() + mock<StickyKeysLogger>(), ) setStickyKeySetting(enabled = false) viewModel = @@ -114,7 +113,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { verify(inputManager) .registerStickyModifierStateListener( any(), - any(InputManager.StickyModifierStateListener::class.java) + any(InputManager.StickyModifierStateListener::class.java), ) } } @@ -187,11 +186,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { assertThat(stickyKeys) .isEqualTo( - mapOf( - ALT to Locked(false), - META to Locked(false), - SHIFT to Locked(false), - ) + mapOf(ALT to Locked(false), META to Locked(false), SHIFT to Locked(false)) ) } } @@ -218,7 +213,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { mapOf( META to false, SHIFT to false, // shift is sticky but not locked - CTRL to false + CTRL to false, ) ) val previousShiftIndex = stickyKeys?.toList()?.indexOf(SHIFT to Locked(false)) @@ -228,7 +223,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { SHIFT to false, SHIFT to true, // shift is now locked META to false, - CTRL to false + CTRL to false, ) ) assertThat(stickyKeys?.toList()?.indexOf(SHIFT to Locked(true))) 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 6e16705b0739..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 @@ -34,6 +34,8 @@ import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticati import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.Idle +import com.android.systemui.scene.data.repository.Transition import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.data.repository.setSceneTransition import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -115,9 +117,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { assertEquals( listOf( - false, // We should start with the surface invisible on LOCKSCREEN. + false // We should start with the surface invisible on LOCKSCREEN. ), - values + values, ) val lockscreenSpecificSurfaceVisibility = true @@ -134,13 +136,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { // We started a transition from LOCKSCREEN, we should be using the value emitted by the // lockscreenSurfaceVisibilityFlow. - assertEquals( - listOf( - false, - lockscreenSpecificSurfaceVisibility, - ), - values - ) + assertEquals(listOf(false, lockscreenSpecificSurfaceVisibility), values) // Go back to LOCKSCREEN, since we won't emit 'true' twice in a row. transitionRepository.sendTransitionStep( @@ -166,7 +162,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { lockscreenSpecificSurfaceVisibility, false, // FINISHED (LOCKSCREEN) ), - values + values, ) val bouncerSpecificVisibility = true @@ -191,13 +187,13 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { false, bouncerSpecificVisibility, ), - values + values, ) } @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) @@ -212,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, @@ -224,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, @@ -238,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)) @@ -249,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) @@ -362,20 +321,14 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { kosmos.sceneInteractor.changeScene(Scenes.Gone, "") assertThat(currentScene).isEqualTo(Scenes.Gone) - listOf( - Scenes.Shade, - Scenes.QuickSettings, - Scenes.Shade, - Scenes.Gone, - ) - .forEach { scene -> - kosmos.setSceneTransition(ObservableTransitionState.Idle(scene)) - kosmos.sceneInteractor.changeScene(scene, "") - assertThat(currentScene).isEqualTo(scene) - assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"") - .that(isSurfaceBehindVisible) - .isTrue() - } + listOf(Scenes.Shade, Scenes.QuickSettings, Scenes.Shade, Scenes.Gone).forEach { scene -> + kosmos.setSceneTransition(ObservableTransitionState.Idle(scene)) + kosmos.sceneInteractor.changeScene(scene, "") + assertThat(currentScene).isEqualTo(scene) + assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"") + .that(isSurfaceBehindVisible) + .isTrue() + } } @Test @@ -386,19 +339,14 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - listOf( - Scenes.Shade, - Scenes.QuickSettings, - Scenes.Shade, - Scenes.Lockscreen, - ) - .forEach { scene -> - kosmos.setSceneTransition(ObservableTransitionState.Idle(scene)) - kosmos.sceneInteractor.changeScene(scene, "") - assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"") - .that(isSurfaceBehindVisible) - .isFalse() - } + listOf(Scenes.Shade, Scenes.QuickSettings, Scenes.Shade, Scenes.Lockscreen).forEach { + scene -> + kosmos.setSceneTransition(ObservableTransitionState.Idle(scene)) + kosmos.sceneInteractor.changeScene(scene, "") + assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"") + .that(isSurfaceBehindVisible) + .isFalse() + } } @Test @@ -427,9 +375,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { assertEquals( listOf( - false, // Not using the animation when we're just sitting on LOCKSCREEN. + false // Not using the animation when we're just sitting on LOCKSCREEN. ), - values + values, ) surfaceBehindIsAnimatingFlow.emit(true) @@ -437,7 +385,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE, - testScope + testScope, ) runCurrent() @@ -446,7 +394,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { false, true, // Still true when we're FINISHED -> GONE, since we're still animating. ), - values + values, ) surfaceBehindIsAnimatingFlow.emit(false) @@ -458,7 +406,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { true, false, // False once the animation ends. ), - values + values, ) } @@ -488,9 +436,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { assertEquals( listOf( - false, // Not using the animation when we're just sitting on LOCKSCREEN. + false // Not using the animation when we're just sitting on LOCKSCREEN. ), - values + values, ) surfaceBehindIsAnimatingFlow.emit(true) @@ -509,7 +457,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { false, true, // We're happily animating while transitioning to gone. ), - values + values, ) // Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE. @@ -536,7 +484,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { true, false, // Despite the animator still running, this should be false. ), - values + values, ) surfaceBehindIsAnimatingFlow.emit(false) @@ -548,7 +496,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { true, false, // The animator ending should have no effect. ), - values + values, ) } @@ -579,10 +527,10 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { assertEquals( listOf( - true, // Unsurprisingly, we should start with the lockscreen visible on + true // Unsurprisingly, we should start with the lockscreen visible on // LOCKSCREEN. ), - values + values, ) transitionRepository.sendTransitionStep( @@ -596,9 +544,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { assertEquals( listOf( - true, // Lockscreen remains visible while we're transitioning to GONE. + true // Lockscreen remains visible while we're transitioning to GONE. ), - values + values, ) transitionRepository.sendTransitionStep( @@ -615,7 +563,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { true, false, // Once we're fully GONE, the lockscreen should not be visible. ), - values + values, ) } @@ -628,7 +576,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE, - testScope + testScope, ) runCurrent() @@ -640,7 +588,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { // Then, false, since we finish in GONE. false, ), - values + values, ) transitionRepository.sendTransitionStep( @@ -665,7 +613,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { // Should remain false as we transition from GONE. false, ), - values + values, ) transitionRepository.sendTransitionStep( @@ -693,7 +641,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { // visibility of the from state (LS). true, ), - values + values, ) transitionRepository.sendTransitionStep( @@ -706,14 +654,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { runCurrent() - assertEquals( - listOf( - true, - false, - true, - ), - values - ) + assertEquals(listOf(true, false, true), values) } /** @@ -730,7 +671,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE, - testScope + testScope, ) runCurrent() @@ -740,7 +681,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { // Not visible since we're GONE. false, ), - values + values, ) transitionRepository.sendTransitionStep( @@ -803,7 +744,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { // STARTED to GONE after a CANCELED from GONE. false, ), - values + values, ) transitionRepository.sendTransitionSteps( @@ -820,7 +761,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { // visible again once we're finished in LOCKSCREEN. true, ), - values + values, ) } @@ -833,7 +774,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE, - testScope + testScope, ) runCurrent() @@ -843,7 +784,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { // Not visible when finished in GONE. false, ), - values + values, ) transitionRepository.sendTransitionStep( @@ -869,7 +810,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { // Still not visible during GONE -> AOD. false, ), - values + values, ) transitionRepository.sendTransitionStep( @@ -886,9 +827,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { true, false, // Visible now that we're FINISHED in AOD. - true + true, ), - values + values, ) transitionRepository.sendTransitionStep( @@ -914,9 +855,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { true, false, // Remains visible from AOD during transition. - true + true, ), - values + values, ) transitionRepository.sendTransitionStep( @@ -934,15 +875,15 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { false, true, // Until we're finished in GONE again. - false + false, ), - values + values, ) } @Test @EnableSceneContainer - fun lockscreenVisibility() = + fun lockscreenVisibilityWithScenes() = testScope.runTest { val isDeviceUnlocked by collectLastValue( @@ -956,32 +897,69 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility) assertThat(lockscreenVisibility).isTrue() + kosmos.setSceneTransition(Idle(Scenes.Shade)) + kosmos.sceneInteractor.changeScene(Scenes.Shade, "") + assertThat(currentScene).isEqualTo(Scenes.Shade) + assertThat(lockscreenVisibility).isTrue() + + kosmos.setSceneTransition(Transition(from = Scenes.Shade, to = Scenes.QuickSettings)) + assertThat(lockscreenVisibility).isTrue() + + kosmos.setSceneTransition(Idle(Scenes.QuickSettings)) + kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "") + assertThat(currentScene).isEqualTo(Scenes.QuickSettings) + assertThat(lockscreenVisibility).isTrue() + + kosmos.setSceneTransition(Transition(from = Scenes.QuickSettings, to = Scenes.Shade)) + assertThat(lockscreenVisibility).isTrue() + + kosmos.setSceneTransition(Idle(Scenes.Shade)) + kosmos.sceneInteractor.changeScene(Scenes.Shade, "") + assertThat(currentScene).isEqualTo(Scenes.Shade) + assertThat(lockscreenVisibility).isTrue() + + kosmos.setSceneTransition(Idle(Scenes.Bouncer)) kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "") assertThat(currentScene).isEqualTo(Scenes.Bouncer) assertThat(lockscreenVisibility).isTrue() + kosmos.setSceneTransition(Transition(from = Scenes.Bouncer, to = Scenes.Gone)) + assertThat(lockscreenVisibility).isTrue() + + kosmos.setSceneTransition(Idle(Scenes.Gone)) kosmos.authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) assertThat(isDeviceUnlocked).isTrue() kosmos.sceneInteractor.changeScene(Scenes.Gone, "") assertThat(currentScene).isEqualTo(Scenes.Gone) assertThat(lockscreenVisibility).isFalse() + kosmos.setSceneTransition(Idle(Scenes.Shade)) kosmos.sceneInteractor.changeScene(Scenes.Shade, "") assertThat(currentScene).isEqualTo(Scenes.Shade) assertThat(lockscreenVisibility).isFalse() + kosmos.setSceneTransition(Transition(from = Scenes.Shade, to = Scenes.QuickSettings)) + assertThat(lockscreenVisibility).isFalse() + + kosmos.setSceneTransition(Idle(Scenes.QuickSettings)) kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "") assertThat(currentScene).isEqualTo(Scenes.QuickSettings) assertThat(lockscreenVisibility).isFalse() + kosmos.setSceneTransition(Idle(Scenes.Shade)) kosmos.sceneInteractor.changeScene(Scenes.Shade, "") assertThat(currentScene).isEqualTo(Scenes.Shade) assertThat(lockscreenVisibility).isFalse() + kosmos.setSceneTransition(Idle(Scenes.Gone)) kosmos.sceneInteractor.changeScene(Scenes.Gone, "") assertThat(currentScene).isEqualTo(Scenes.Gone) assertThat(lockscreenVisibility).isFalse() + kosmos.setSceneTransition(Transition(from = Scenes.Gone, to = Scenes.Lockscreen)) + assertThat(lockscreenVisibility).isFalse() + + kosmos.setSceneTransition(Idle(Scenes.Lockscreen)) kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "") assertThat(currentScene).isEqualTo(Scenes.Lockscreen) assertThat(lockscreenVisibility).isTrue() @@ -1037,7 +1015,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { flowOf(Scenes.Lockscreen), progress, false, - flowOf(false) + flowOf(false), ) private val goneToLs = @@ -1047,7 +1025,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { flowOf(Scenes.Lockscreen), progress, false, - flowOf(false) + flowOf(false), ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt index 9c58e2b987a1..92764ae94271 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt @@ -29,11 +29,13 @@ import com.android.wm.shell.keyguard.KeyguardTransitions import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.anyInt import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any @SmallTest @RunWith(AndroidJUnit4::class) @@ -112,4 +114,20 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { verify(activityTaskManagerService).setLockScreenShown(true, false) verifyNoMoreInteractions(activityTaskManagerService) } + + @Test + fun setSurfaceBehindVisibility_goesAwayFirst_andIgnoresSecondCall() { + underTest.setLockscreenShown(true) + underTest.setSurfaceBehindVisibility(true) + verify(activityTaskManagerService).keyguardGoingAway(0) + + underTest.setSurfaceBehindVisibility(true) + verifyNoMoreInteractions(keyguardTransitions) + } + + @Test + fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility() { + underTest.setSurfaceBehindVisibility(false) + verify(activityTaskManagerService).setLockScreenShown(eq(true), any()) + } } 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/media/controls/domain/pipeline/Media3ActionFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt new file mode 100644 index 000000000000..9e3fdf377b83 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt @@ -0,0 +1,335 @@ +/* + * 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.media.controls.domain.pipeline + +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 +import androidx.media3.session.CommandButton +import androidx.media3.session.MediaController as Media3Controller +import androidx.media3.session.SessionCommand +import androidx.media3.session.SessionToken +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.graphics.imageLoader +import com.android.systemui.kosmos.testScope +import com.android.systemui.media.controls.shared.mediaLogger +import com.android.systemui.media.controls.shared.model.MediaButton +import com.android.systemui.media.controls.util.fakeMediaControllerFactory +import com.android.systemui.media.controls.util.fakeSessionTokenFactory +import com.android.systemui.res.R +import com.android.systemui.testKosmos +import com.google.common.collect.ImmutableList +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +private const val PACKAGE_NAME = "package_name" +private const val CUSTOM_ACTION_NAME = "Custom Action" +private const val CUSTOM_ACTION_COMMAND = "custom-action" + +@SmallTest +@RunWithLooper +@RunWith(AndroidJUnit4::class) +class Media3ActionFactoryTest : SysuiTestCase() { + + private val kosmos = testKosmos() + 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 legacyToken = MediaSession.Token(1, null) + private val token = mock<SessionToken>() + private val handler = + mock<Handler> { + on { post(runnableCaptor.capture()) } doAnswer + { + runnableCaptor.lastValue.run() + true + } + } + private val customLayout = ImmutableList.of<CommandButton>() + private val media3Controller = + mock<Media3Controller> { + on { customLayout } doReturn customLayout + on { sessionExtras } doReturn Bundle() + on { isCommandAvailable(any()) } doReturn true + on { isSessionCommandAvailable(any<SessionCommand>()) } doReturn true + } + + private lateinit var underTest: Media3ActionFactory + + @Before + fun setup() { + testableLooper = TestableLooper.get(this) + + underTest = + Media3ActionFactory( + context, + kosmos.imageLoader, + controllerFactory, + tokenFactory, + kosmos.mediaLogger, + kosmos.looper, + handler, + kosmos.testScope, + ) + + controllerFactory.setMedia3Controller(media3Controller) + tokenFactory.setMedia3SessionToken(token) + } + + @Test + fun media3Actions_playingState_withCustomActions() = + testScope.runTest { + // Media is playing, all commands available, with custom actions + val customLayout = ImmutableList.copyOf((0..1).map { createCustomCommandButton(it) }) + whenever(media3Controller.customLayout).thenReturn(customLayout) + whenever(media3Controller.isPlaying).thenReturn(true) + val result = getActions() + + assertThat(result).isNotNull() + + val actions = result!! + assertThat(actions.playOrPause!!.contentDescription) + .isEqualTo(context.getString(R.string.controls_media_button_pause)) + actions.playOrPause!!.action!!.run() + runCurrent() + verify(media3Controller).pause() + verify(media3Controller).release() + clearInvocations(media3Controller) + + assertThat(actions.prevOrCustom!!.contentDescription) + .isEqualTo(context.getString(R.string.controls_media_button_prev)) + actions.prevOrCustom!!.action!!.run() + runCurrent() + verify(media3Controller).seekToPrevious() + verify(media3Controller).release() + clearInvocations(media3Controller) + + assertThat(actions.nextOrCustom!!.contentDescription) + .isEqualTo(context.getString(R.string.controls_media_button_next)) + actions.nextOrCustom!!.action!!.run() + runCurrent() + verify(media3Controller).seekToNext() + verify(media3Controller).release() + clearInvocations(media3Controller) + + assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0") + actions.custom0!!.action!!.run() + runCurrent() + verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>()) + assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0") + verify(media3Controller).release() + clearInvocations(media3Controller) + + assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1") + actions.custom1!!.action!!.run() + runCurrent() + verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>()) + assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1") + verify(media3Controller).release() + } + + @Test + fun media3Actions_pausedState_hasPauseAction() = + testScope.runTest { + whenever(media3Controller.isPlaying).thenReturn(false) + val result = getActions() + + assertThat(result).isNotNull() + val actions = result!! + assertThat(actions.playOrPause!!.contentDescription) + .isEqualTo(context.getString(R.string.controls_media_button_play)) + clearInvocations(media3Controller) + + actions.playOrPause!!.action!!.run() + runCurrent() + verify(media3Controller).play() + verify(media3Controller).release() + clearInvocations(media3Controller) + } + + @Test + fun media3Actions_bufferingState_hasLoadingSpinner() = + testScope.runTest { + whenever(media3Controller.isPlaying).thenReturn(false) + whenever(media3Controller.playbackState).thenReturn(Player.STATE_BUFFERING) + val result = getActions() + + assertThat(result).isNotNull() + val actions = result!! + assertThat(actions.playOrPause!!.contentDescription) + .isEqualTo(context.getString(R.string.controls_media_button_connecting)) + assertThat(actions.playOrPause!!.action).isNull() + assertThat(actions.playOrPause!!.rebindId) + .isEqualTo(com.android.internal.R.drawable.progress_small_material) + } + + @Test + fun media3Actions_noPrevNext_usesCustom() = + testScope.runTest { + val customLayout = ImmutableList.copyOf((0..4).map { createCustomCommandButton(it) }) + whenever(media3Controller.customLayout).thenReturn(customLayout) + whenever(media3Controller.isPlaying).thenReturn(true) + whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_PREVIOUS))) + .thenReturn(false) + whenever( + media3Controller.isCommandAvailable( + eq(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) + ) + ) + .thenReturn(false) + whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT))) + .thenReturn(false) + whenever( + media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)) + ) + .thenReturn(false) + val result = getActions() + + assertThat(result).isNotNull() + val actions = result!! + + assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0") + actions.prevOrCustom!!.action!!.run() + runCurrent() + verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>()) + assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0") + verify(media3Controller).release() + clearInvocations(media3Controller) + + assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1") + actions.nextOrCustom!!.action!!.run() + runCurrent() + verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>()) + assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1") + verify(media3Controller).release() + clearInvocations(media3Controller) + + 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() + clearInvocations(media3Controller) + + assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 3") + actions.custom1!!.action!!.run() + runCurrent() + verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>()) + assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 3") + verify(media3Controller).release() + } + + @Test + fun media3Actions_noPrevNext_reservedSpace() = + testScope.runTest { + val customLayout = ImmutableList.copyOf((0..4).map { createCustomCommandButton(it) }) + whenever(media3Controller.customLayout).thenReturn(customLayout) + whenever(media3Controller.isPlaying).thenReturn(true) + whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_PREVIOUS))) + .thenReturn(false) + whenever( + media3Controller.isCommandAvailable( + eq(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) + ) + ) + .thenReturn(false) + whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT))) + .thenReturn(false) + whenever( + media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)) + ) + .thenReturn(false) + val extras = + Bundle().apply { + putBoolean( + MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, + true, + ) + putBoolean( + MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, + true, + ) + } + whenever(media3Controller.sessionExtras).thenReturn(extras) + val result = getActions() + + assertThat(result).isNotNull() + val actions = result!! + + assertThat(actions.prevOrCustom).isNull() + assertThat(actions.nextOrCustom).isNull() + + assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0") + actions.custom0!!.action!!.run() + runCurrent() + verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>()) + assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0") + verify(media3Controller).release() + clearInvocations(media3Controller) + + assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1") + actions.custom1!!.action!!.run() + runCurrent() + verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>()) + assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1") + verify(media3Controller).release() + } + + private suspend fun getActions(): MediaButton? { + val result = underTest.createActionsFromSession(PACKAGE_NAME, legacyToken) + testScope.runCurrent() + verify(media3Controller).release() + + // Clear so tests can verify the correct number of release() calls in later operations + clearInvocations(media3Controller) + return result + } + + private fun createCustomCommandButton(id: Int): CommandButton { + return CommandButton.Builder() + .setDisplayName("$CUSTOM_ACTION_NAME $id") + .setSessionCommand(SessionCommand("$CUSTOM_ACTION_COMMAND $id", Bundle())) + .build() + } +} 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 fc9e595945dd..1a7265b09aae 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 @@ -29,6 +29,7 @@ import android.media.session.MediaSession import android.media.session.PlaybackState import android.os.Bundle import android.service.notification.StatusBarNotification +import android.testing.TestableLooper.RunWithLooper import androidx.media.utils.MediaConstants import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -69,6 +70,7 @@ private const val SESSION_TITLE = "title" private const val SESSION_EMPTY_TITLE = "" @SmallTest +@RunWithLooper @RunWith(AndroidJUnit4::class) class MediaDataLoaderTest : SysuiTestCase() { @@ -80,6 +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 val session = MediaSession(context, "MediaDataLoaderTestSession") private val metadataBuilder = MediaMetadata.Builder().apply { @@ -87,21 +90,25 @@ class MediaDataLoaderTest : SysuiTestCase() { putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) } - private val underTest: MediaDataLoader = - MediaDataLoader( - context, - testDispatcher, - testScope, - mediaControllerFactory, - mediaFlags, - kosmos.imageLoader, - statusBarManager, - ) + private lateinit var underTest: MediaDataLoader @Before fun setUp() { mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController) + whenever(mediaController.sessionToken).thenReturn(session.sessionToken) whenever(mediaController.metadata).then { metadataBuilder.build() } + + underTest = + MediaDataLoader( + context, + testDispatcher, + testScope, + mediaControllerFactory, + mediaFlags, + kosmos.imageLoader, + statusBarManager, + kosmos.media3ActionFactory, + ) } @Test @@ -394,6 +401,7 @@ class MediaDataLoaderTest : SysuiTestCase() { mediaFlags, mockImageLoader, statusBarManager, + media3ActionFactory, ) metadataBuilder.putString( MediaMetadata.METADATA_KEY_ALBUM_ART_URI, @@ -422,6 +430,7 @@ class MediaDataLoaderTest : SysuiTestCase() { mediaFlags, mockImageLoader, statusBarManager, + media3ActionFactory, ) metadataBuilder.putString( MediaMetadata.METADATA_KEY_ALBUM_ART_URI, 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/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java index 2905a7329d21..646722bee35f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java @@ -116,6 +116,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.data.repository.LightBarControllerStore; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.LightBarController; @@ -208,7 +209,7 @@ public class NavigationBarTest extends SysuiTestCase { @Mock private LightBarController mLightBarController; @Mock - private LightBarController.Factory mLightBarcontrollerFactory; + private LightBarControllerStore mLightBarControllerStore; @Mock private AutoHideController mAutoHideController; @Mock @@ -257,7 +258,7 @@ public class NavigationBarTest extends SysuiTestCase { public void setup() throws Exception { MockitoAnnotations.initMocks(this); - when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController); + when(mLightBarControllerStore.forDisplay(anyInt())).thenReturn(mLightBarController); when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController); when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton); when(mNavigationBarView.getRecentsButton()).thenReturn(mRecentsButton); @@ -649,8 +650,7 @@ public class NavigationBarTest extends SysuiTestCase { mFakeExecutor, mUiEventLogger, mNavBarHelper, - mLightBarController, - mLightBarcontrollerFactory, + mLightBarControllerStore, mAutoHideController, mAutoHideControllerFactory, Optional.of(mTelecomManager), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt index ff40e43e2c8c..a06353171c33 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt @@ -115,7 +115,7 @@ class QSTileLoggerTest : SysuiTestCase() { underTest.logUserActionPipeline( TileSpec.create("test_spec"), QSTileUserAction.Click(null), - QSTileState.build({ Icon.Resource(0, ContentDescription.Resource(0)) }, "") {}, + QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}, "test_data", ) @@ -141,7 +141,7 @@ class QSTileLoggerTest : SysuiTestCase() { fun testLogStateUpdate() { underTest.logStateUpdate( TileSpec.create("test_spec"), - QSTileState.build({ Icon.Resource(0, ContentDescription.Resource(0)) }, "") {}, + QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}, "test_data", ) @@ -162,18 +162,14 @@ class QSTileLoggerTest : SysuiTestCase() { @Test fun testLogForceUpdate() { - underTest.logForceUpdate( - TileSpec.create("test_spec"), - ) + underTest.logForceUpdate(TileSpec.create("test_spec")) assertThat(logBuffer.getStringBuffer()).contains("tile data force update") } @Test fun testLogInitialUpdate() { - underTest.logInitialRequest( - TileSpec.create("test_spec"), - ) + underTest.logInitialRequest(TileSpec.create("test_spec")) assertThat(logBuffer.getStringBuffer()).contains("tile data initial update") } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt index c918ed82604c..056efb34a0b1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt @@ -85,8 +85,8 @@ class QSTileViewModelImplTest : SysuiTestCase() { object : QSTileDataToStateMapper<Any> { override fun map(config: QSTileConfig, data: Any): QSTileState = QSTileState.build( - { Icon.Resource(0, ContentDescription.Resource(0)) }, - data.toString() + Icon.Resource(0, ContentDescription.Resource(0)), + data.toString(), ) {} } }, @@ -116,7 +116,7 @@ class QSTileViewModelImplTest : SysuiTestCase() { .isEqualTo( "test_spec:\n" + " QSTileState(" + - "icon=() -> com.android.systemui.common.shared.model.Icon?, " + + "icon=Resource(res=0, contentDescription=Resource(res=0)), " + "iconRes=null, " + "label=test_data, " + "activationState=INACTIVE, " + diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt index 5a73fe28ee18..00460bfe83b2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt @@ -66,7 +66,7 @@ class AirplaneModeMapperTest : SysuiTestCase() { createAirplaneModeState( QSTileState.ActivationState.ACTIVE, context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_ACTIVE], - R.drawable.qs_airplane_icon_on + R.drawable.qs_airplane_icon_on, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -81,7 +81,7 @@ class AirplaneModeMapperTest : SysuiTestCase() { createAirplaneModeState( QSTileState.ActivationState.INACTIVE, context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_INACTIVE], - R.drawable.qs_airplane_icon_off + R.drawable.qs_airplane_icon_off, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -89,11 +89,11 @@ class AirplaneModeMapperTest : SysuiTestCase() { private fun createAirplaneModeState( activationState: QSTileState.ActivationState, secondaryLabel: String, - iconRes: Int + iconRes: Int, ): QSTileState { val label = context.getString(R.string.airplane_mode) return QSTileState( - { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + Icon.Loaded(context.getDrawable(iconRes)!!, null), iconRes, label, activationState, @@ -103,7 +103,7 @@ class AirplaneModeMapperTest : SysuiTestCase() { null, QSTileState.SideViewIcon.None, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt index 79e4fef874b6..632aae035ede 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt @@ -51,7 +51,7 @@ class AlarmTileMapperTest : SysuiTestCase() { .apply { addOverride(R.drawable.ic_alarm, TestStubDrawable()) } .resources, context.theme, - fakeClock + fakeClock, ) } @@ -69,7 +69,7 @@ class AlarmTileMapperTest : SysuiTestCase() { val expectedState = createAlarmTileState( QSTileState.ActivationState.INACTIVE, - context.getString(R.string.qs_alarm_tile_no_alarm) + context.getString(R.string.qs_alarm_tile_no_alarm), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -85,7 +85,7 @@ class AlarmTileMapperTest : SysuiTestCase() { val localDateTime = LocalDateTime.ofInstant( Instant.ofEpochMilli(triggerTime), - TimeZone.getDefault().toZoneId() + TimeZone.getDefault().toZoneId(), ) val expectedSecondaryLabel = AlarmTileMapper.formatter24Hour.format(localDateTime) val expectedState = @@ -104,7 +104,7 @@ class AlarmTileMapperTest : SysuiTestCase() { val localDateTime = LocalDateTime.ofInstant( Instant.ofEpochMilli(triggerTime), - TimeZone.getDefault().toZoneId() + TimeZone.getDefault().toZoneId(), ) val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime) val expectedState = @@ -124,7 +124,7 @@ class AlarmTileMapperTest : SysuiTestCase() { val localDateTime = LocalDateTime.ofInstant( Instant.ofEpochMilli(triggerTime), - TimeZone.getDefault().toZoneId() + TimeZone.getDefault().toZoneId(), ) val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime) val expectedState = @@ -144,7 +144,7 @@ class AlarmTileMapperTest : SysuiTestCase() { val localDateTime = LocalDateTime.ofInstant( Instant.ofEpochMilli(triggerTime), - TimeZone.getDefault().toZoneId() + TimeZone.getDefault().toZoneId(), ) val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime) val expectedState = @@ -164,7 +164,7 @@ class AlarmTileMapperTest : SysuiTestCase() { val localDateTime = LocalDateTime.ofInstant( Instant.ofEpochMilli(triggerTime), - TimeZone.getDefault().toZoneId() + TimeZone.getDefault().toZoneId(), ) val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime) val expectedState = @@ -174,11 +174,11 @@ class AlarmTileMapperTest : SysuiTestCase() { private fun createAlarmTileState( activationState: QSTileState.ActivationState, - secondaryLabel: String + secondaryLabel: String, ): QSTileState { val label = context.getString(R.string.status_bar_alarm) return QSTileState( - { Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null) }, + Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null), R.drawable.ic_alarm, label, activationState, @@ -188,7 +188,7 @@ class AlarmTileMapperTest : SysuiTestCase() { null, QSTileState.SideViewIcon.Chevron, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt index a0d26c28cbfa..5385f945946c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt @@ -253,7 +253,7 @@ class BatterySaverTileMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(R.string.battery_detail_switch_title) return QSTileState( - { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + Icon.Loaded(context.getDrawable(iconRes)!!, null), iconRes, label, activationState, @@ -265,7 +265,7 @@ class BatterySaverTileMapperTest : SysuiTestCase() { stateDescription, QSTileState.SideViewIcon.None, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt index ea7b7c5f797d..356b98eb192e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt @@ -45,7 +45,7 @@ class ColorCorrectionTileMapperTest : SysuiTestCase() { context.orCreateTestableResources .apply { addOverride(R.drawable.ic_qs_color_correction, TestStubDrawable()) } .resources, - context.theme + context.theme, ) } @@ -73,11 +73,11 @@ class ColorCorrectionTileMapperTest : SysuiTestCase() { private fun createColorCorrectionTileState( activationState: QSTileState.ActivationState, - secondaryLabel: String + secondaryLabel: String, ): QSTileState { val label = context.getString(R.string.quick_settings_color_correction_label) return QSTileState( - { Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null) }, + Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null), R.drawable.ic_qs_color_correction, label, activationState, @@ -87,7 +87,7 @@ class ColorCorrectionTileMapperTest : SysuiTestCase() { null, QSTileState.SideViewIcon.None, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt index f1d08c068150..8236c4c1e638 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt @@ -57,10 +57,7 @@ class CustomTileMapperTest : SysuiTestCase() { private val kosmos = testKosmos().apply { customTileSpec = TileSpec.Companion.create(TEST_COMPONENT) } private val underTest by lazy { - CustomTileMapper( - context = mockContext, - uriGrantsManager = uriGrantsManager, - ) + CustomTileMapper(context = mockContext, uriGrantsManager = uriGrantsManager) } @Test @@ -68,10 +65,7 @@ class CustomTileMapperTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val actual = - underTest.map( - customTileQsTileConfig, - createModel(hasPendingBind = true), - ) + underTest.map(customTileQsTileConfig, createModel(hasPendingBind = true)) val expected = createTileState( activationState = QSTileState.ActivationState.UNAVAILABLE, @@ -91,10 +85,7 @@ class CustomTileMapperTest : SysuiTestCase() { customTileQsTileConfig, createModel(tileState = Tile.STATE_ACTIVE), ) - val expected = - createTileState( - activationState = QSTileState.ActivationState.ACTIVE, - ) + val expected = createTileState(activationState = QSTileState.ActivationState.ACTIVE) assertThat(actual).isEqualTo(expected) } @@ -110,9 +101,7 @@ class CustomTileMapperTest : SysuiTestCase() { createModel(tileState = Tile.STATE_INACTIVE), ) val expected = - createTileState( - activationState = QSTileState.ActivationState.INACTIVE, - ) + createTileState(activationState = QSTileState.ActivationState.INACTIVE) assertThat(actual).isEqualTo(expected) } @@ -142,10 +131,7 @@ class CustomTileMapperTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val actual = - underTest.map( - customTileQsTileConfig, - createModel(isToggleable = false), - ) + underTest.map(customTileQsTileConfig, createModel(isToggleable = false)) val expected = createTileState( sideIcon = QSTileState.SideViewIcon.Chevron, @@ -184,7 +170,7 @@ class CustomTileMapperTest : SysuiTestCase() { customTileQsTileConfig, createModel( tileIcon = createIcon(RuntimeException(), false), - defaultTileIcon = createIcon(null, true) + defaultTileIcon = createIcon(null, true), ), ) val expected = @@ -266,7 +252,7 @@ class CustomTileMapperTest : SysuiTestCase() { a11yClass: String? = Switch::class.qualifiedName, ): QSTileState { return QSTileState( - { icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) } }, + icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) }, null, "test label", activationState, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt index 63fb67d432f0..587585ccee2e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt @@ -44,7 +44,7 @@ class FlashlightMapperTest : SysuiTestCase() { addOverride(R.drawable.qs_flashlight_icon_on, TestStubDrawable()) } .resources, - context.theme + context.theme, ) } @@ -74,7 +74,7 @@ class FlashlightMapperTest : SysuiTestCase() { val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null) - val actualIcon = tileState.icon() + val actualIcon = tileState.icon assertThat(actualIcon).isEqualTo(expectedIcon) } @@ -85,7 +85,7 @@ class FlashlightMapperTest : SysuiTestCase() { val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null) - val actualIcon = tileState.icon() + val actualIcon = tileState.icon assertThat(actualIcon).isEqualTo(expectedIcon) } @@ -96,7 +96,7 @@ class FlashlightMapperTest : SysuiTestCase() { val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null) - val actualIcon = tileState.icon() + val actualIcon = tileState.icon assertThat(actualIcon).isEqualTo(expectedIcon) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt index f8e01be5163f..e81771ec38d5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt @@ -42,7 +42,7 @@ class FontScalingTileMapperTest : SysuiTestCase() { context.orCreateTestableResources .apply { addOverride(R.drawable.ic_qs_font_scaling, TestStubDrawable()) } .resources, - context.theme + context.theme, ) } @@ -58,14 +58,7 @@ class FontScalingTileMapperTest : SysuiTestCase() { private fun createFontScalingTileState(): QSTileState = QSTileState( - { - Icon.Loaded( - context.getDrawable( - R.drawable.ic_qs_font_scaling, - )!!, - null - ) - }, + Icon.Loaded(context.getDrawable(R.drawable.ic_qs_font_scaling)!!, null), R.drawable.ic_qs_font_scaling, context.getString(R.string.quick_settings_font_scaling_label), QSTileState.ActivationState.ACTIVE, @@ -75,6 +68,6 @@ class FontScalingTileMapperTest : SysuiTestCase() { null, QSTileState.SideViewIcon.Chevron, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt index cdf6bda91301..12d604ff6a7c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt @@ -102,7 +102,7 @@ class HearingDevicesTileMapperTest : SysuiTestCase() { val label = context.getString(R.string.quick_settings_hearing_devices_label) val iconRes = R.drawable.qs_hearing_devices_icon return QSTileState( - { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + Icon.Loaded(context.getDrawable(iconRes)!!, null), iconRes, label, activationState, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt index d32ba47204c0..9dcf49e02697 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt @@ -187,7 +187,7 @@ class InternetTileMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(R.string.quick_settings_internet_label) return QSTileState( - { icon }, + icon, iconRes, label, activationState, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt index a7bd69770a4f..30fce73e04da 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt @@ -49,7 +49,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() { addOverride(R.drawable.qs_invert_colors_icon_on, TestStubDrawable()) } .resources, - context.theme + context.theme, ) } @@ -63,7 +63,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() { createColorInversionTileState( QSTileState.ActivationState.INACTIVE, subtitleArray[1], - R.drawable.qs_invert_colors_icon_off + R.drawable.qs_invert_colors_icon_off, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -78,7 +78,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() { createColorInversionTileState( QSTileState.ActivationState.ACTIVE, subtitleArray[2], - R.drawable.qs_invert_colors_icon_on + R.drawable.qs_invert_colors_icon_on, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -90,7 +90,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(R.string.quick_settings_inversion_label) return QSTileState( - { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + Icon.Loaded(context.getDrawable(iconRes)!!, null), iconRes, label, activationState, @@ -100,7 +100,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() { null, QSTileState.SideViewIcon.None, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt index ea74a4c0d398..37e8a6053682 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt @@ -45,7 +45,7 @@ class LocationTileMapperTest : SysuiTestCase() { addOverride(R.drawable.qs_location_icon_on, TestStubDrawable()) } .resources, - context.theme + context.theme, ) } @@ -70,7 +70,7 @@ class LocationTileMapperTest : SysuiTestCase() { val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true)) val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_on)!!, null) - val actualIcon = tileState.icon() + val actualIcon = tileState.icon Truth.assertThat(actualIcon).isEqualTo(expectedIcon) } @@ -79,7 +79,7 @@ class LocationTileMapperTest : SysuiTestCase() { val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false)) val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_off)!!, null) - val actualIcon = tileState.icon() + val actualIcon = tileState.icon Truth.assertThat(actualIcon).isEqualTo(expectedIcon) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt index c3d45dbbd09a..4e91d16bf1ec 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt @@ -59,34 +59,24 @@ class ModesTileMapperTest : SysuiTestCase() { @Test fun inactiveState() { val icon = TestStubDrawable("res123").asIcon() - val model = - ModesTileModel( - isActivated = false, - activeModes = emptyList(), - icon = icon, - ) + val model = ModesTileModel(isActivated = false, activeModes = emptyList(), icon = icon) val state = underTest.map(config, model) assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE) - assertThat(state.icon()).isEqualTo(icon) + assertThat(state.icon).isEqualTo(icon) assertThat(state.secondaryLabel).isEqualTo("No active modes") } @Test fun activeState_oneMode() { val icon = TestStubDrawable("res123").asIcon() - val model = - ModesTileModel( - isActivated = true, - activeModes = listOf("DND"), - icon = icon, - ) + val model = ModesTileModel(isActivated = true, activeModes = listOf("DND"), icon = icon) val state = underTest.map(config, model) assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE) - assertThat(state.icon()).isEqualTo(icon) + assertThat(state.icon).isEqualTo(icon) assertThat(state.secondaryLabel).isEqualTo("DND is active") } @@ -103,7 +93,7 @@ class ModesTileMapperTest : SysuiTestCase() { val state = underTest.map(config, model) assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE) - assertThat(state.icon()).isEqualTo(icon) + assertThat(state.icon).isEqualTo(icon) assertThat(state.secondaryLabel).isEqualTo("3 modes are active") } @@ -115,12 +105,12 @@ class ModesTileMapperTest : SysuiTestCase() { isActivated = false, activeModes = emptyList(), icon = icon, - iconResId = 123 + iconResId = 123, ) val state = underTest.map(config, model) - assertThat(state.icon()).isEqualTo(icon) + assertThat(state.icon).isEqualTo(icon) assertThat(state.iconRes).isEqualTo(123) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt index 75273f2a52e1..1457f533f5ec 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt @@ -73,7 +73,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() { val expectedState = createNightDisplayTileState( QSTileState.ActivationState.INACTIVE, - context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE] + context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE], ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -88,7 +88,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() { val expectedState = createNightDisplayTileState( QSTileState.ActivationState.INACTIVE, - context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE] + context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE], ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -102,7 +102,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() { val expectedState = createNightDisplayTileState( QSTileState.ActivationState.ACTIVE, - context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE] + context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE], ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -116,7 +116,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() { val expectedState = createNightDisplayTileState( QSTileState.ActivationState.ACTIVE, - context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE] + context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE], ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -140,7 +140,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() { val expectedState = createNightDisplayTileState( QSTileState.ActivationState.ACTIVE, - context.getString(R.string.quick_settings_night_secondary_label_until_sunrise) + context.getString(R.string.quick_settings_night_secondary_label_until_sunrise), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -154,7 +154,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() { val expectedState = createNightDisplayTileState( QSTileState.ActivationState.INACTIVE, - context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset) + context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -181,8 +181,8 @@ class NightDisplayTileMapperTest : SysuiTestCase() { QSTileState.ActivationState.INACTIVE, context.getString( R.string.quick_settings_night_secondary_label_on_at, - formatter24Hour.format(testStartTime) - ) + formatter24Hour.format(testStartTime), + ), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -199,8 +199,8 @@ class NightDisplayTileMapperTest : SysuiTestCase() { QSTileState.ActivationState.INACTIVE, context.getString( R.string.quick_settings_night_secondary_label_on_at, - formatter12Hour.format(testStartTime) - ) + formatter12Hour.format(testStartTime), + ), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -218,8 +218,8 @@ class NightDisplayTileMapperTest : SysuiTestCase() { QSTileState.ActivationState.INACTIVE, context.getString( R.string.quick_settings_night_secondary_label_on_at, - formatter12Hour.format(testStartTime) - ) + formatter12Hour.format(testStartTime), + ), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -235,8 +235,8 @@ class NightDisplayTileMapperTest : SysuiTestCase() { QSTileState.ActivationState.ACTIVE, context.getString( R.string.quick_settings_secondary_label_until, - formatter24Hour.format(testEndTime) - ) + formatter24Hour.format(testEndTime), + ), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -252,8 +252,8 @@ class NightDisplayTileMapperTest : SysuiTestCase() { QSTileState.ActivationState.ACTIVE, context.getString( R.string.quick_settings_secondary_label_until, - formatter12Hour.format(testEndTime) - ) + formatter12Hour.format(testEndTime), + ), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -270,15 +270,15 @@ class NightDisplayTileMapperTest : SysuiTestCase() { QSTileState.ActivationState.ACTIVE, context.getString( R.string.quick_settings_secondary_label_until, - formatter24Hour.format(testEndTime) - ) + formatter24Hour.format(testEndTime), + ), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } private fun createNightDisplayTileState( activationState: QSTileState.ActivationState, - secondaryLabel: String? + secondaryLabel: String?, ): QSTileState { val label = context.getString(R.string.quick_settings_night_display_label) val iconRes = @@ -289,7 +289,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() { if (TextUtils.isEmpty(secondaryLabel)) label else TextUtils.concat(label, ", ", secondaryLabel) return QSTileState( - { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + Icon.Loaded(context.getDrawable(iconRes)!!, null), iconRes, label, activationState, @@ -299,7 +299,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() { null, QSTileState.SideViewIcon.None, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt index 3189a9e063a1..7782d2b279a8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt @@ -51,11 +51,11 @@ class OneHandedModeTileMapperTest : SysuiTestCase() { .apply { addOverride( com.android.internal.R.drawable.ic_qs_one_handed_mode, - TestStubDrawable() + TestStubDrawable(), ) } .resources, - context.theme + context.theme, ) } @@ -69,7 +69,7 @@ class OneHandedModeTileMapperTest : SysuiTestCase() { createOneHandedModeTileState( QSTileState.ActivationState.INACTIVE, subtitleArray[1], - com.android.internal.R.drawable.ic_qs_one_handed_mode + com.android.internal.R.drawable.ic_qs_one_handed_mode, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -84,7 +84,7 @@ class OneHandedModeTileMapperTest : SysuiTestCase() { createOneHandedModeTileState( QSTileState.ActivationState.ACTIVE, subtitleArray[2], - com.android.internal.R.drawable.ic_qs_one_handed_mode + com.android.internal.R.drawable.ic_qs_one_handed_mode, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -96,7 +96,7 @@ class OneHandedModeTileMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(R.string.quick_settings_onehanded_label) return QSTileState( - { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + Icon.Loaded(context.getDrawable(iconRes)!!, null), iconRes, label, activationState, @@ -106,7 +106,7 @@ class OneHandedModeTileMapperTest : SysuiTestCase() { null, QSTileState.SideViewIcon.None, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt index 08e5cbef31ab..ed33250a3392 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt @@ -49,11 +49,11 @@ class QRCodeScannerTileMapperTest : SysuiTestCase() { .apply { addOverride( com.android.systemui.res.R.drawable.ic_qr_code_scanner, - TestStubDrawable() + TestStubDrawable(), ) } .resources, - context.theme + context.theme, ) } @@ -64,11 +64,7 @@ class QRCodeScannerTileMapperTest : SysuiTestCase() { val outputState = mapper.map(config, inputModel) - val expectedState = - createQRCodeScannerTileState( - QSTileState.ActivationState.INACTIVE, - null, - ) + val expectedState = createQRCodeScannerTileState(QSTileState.ActivationState.INACTIVE, null) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -83,7 +79,7 @@ class QRCodeScannerTileMapperTest : SysuiTestCase() { QSTileState.ActivationState.UNAVAILABLE, context.getString( com.android.systemui.res.R.string.qr_code_scanner_updating_secondary_label - ) + ), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -94,12 +90,10 @@ class QRCodeScannerTileMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(com.android.systemui.res.R.string.qr_code_scanner_title) return QSTileState( - { - Icon.Loaded( - context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!, - null - ) - }, + Icon.Loaded( + context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!, + null, + ), com.android.systemui.res.R.drawable.ic_qr_code_scanner, label, activationState, @@ -109,7 +103,7 @@ class QRCodeScannerTileMapperTest : SysuiTestCase() { null, QSTileState.SideViewIcon.Chevron, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt index ca30e9ca3e69..85111fd07663 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt @@ -51,7 +51,7 @@ class ReduceBrightColorsTileMapperTest : SysuiTestCase() { addOverride(R.drawable.qs_extra_dim_icon_off, TestStubDrawable()) } .resources, - context.theme + context.theme, ) } @@ -61,10 +61,7 @@ class ReduceBrightColorsTileMapperTest : SysuiTestCase() { val outputState = mapper.map(config, inputModel) - val expectedState = - createReduceBrightColorsTileState( - QSTileState.ActivationState.INACTIVE, - ) + val expectedState = createReduceBrightColorsTileState(QSTileState.ActivationState.INACTIVE) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -79,7 +76,7 @@ class ReduceBrightColorsTileMapperTest : SysuiTestCase() { } private fun createReduceBrightColorsTileState( - activationState: QSTileState.ActivationState, + activationState: QSTileState.ActivationState ): QSTileState { val label = context.getString(com.android.internal.R.string.reduce_bright_colors_feature_name) @@ -88,7 +85,7 @@ class ReduceBrightColorsTileMapperTest : SysuiTestCase() { R.drawable.qs_extra_dim_icon_on else R.drawable.qs_extra_dim_icon_off return QSTileState( - { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + Icon.Loaded(context.getDrawable(iconRes)!!, null), iconRes, label, activationState, @@ -101,7 +98,7 @@ class ReduceBrightColorsTileMapperTest : SysuiTestCase() { null, QSTileState.SideViewIcon.None, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt index 3e40c5ca797c..53671ba38eb6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt @@ -66,13 +66,13 @@ class RotationLockTileMapperTest : SysuiTestCase() { addOverride(com.android.internal.R.bool.config_allowRotationResolver, true) addOverride( com.android.internal.R.array.config_foldedDeviceStates, - intArrayOf() // empty array <=> device is not foldable + intArrayOf(), // empty array <=> device is not foldable ) } .resources, context.theme, devicePostureController, - deviceStateManager + deviceStateManager, ) } @@ -86,7 +86,7 @@ class RotationLockTileMapperTest : SysuiTestCase() { createRotationLockTileState( QSTileState.ActivationState.ACTIVE, EMPTY_SECONDARY_STRING, - R.drawable.qs_auto_rotate_icon_on + R.drawable.qs_auto_rotate_icon_on, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -101,7 +101,7 @@ class RotationLockTileMapperTest : SysuiTestCase() { createRotationLockTileState( QSTileState.ActivationState.ACTIVE, context.getString(R.string.rotation_lock_camera_rotation_on), - R.drawable.qs_auto_rotate_icon_on + R.drawable.qs_auto_rotate_icon_on, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -116,7 +116,7 @@ class RotationLockTileMapperTest : SysuiTestCase() { createRotationLockTileState( QSTileState.ActivationState.INACTIVE, EMPTY_SECONDARY_STRING, - R.drawable.qs_auto_rotate_icon_off + R.drawable.qs_auto_rotate_icon_off, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -167,7 +167,7 @@ class RotationLockTileMapperTest : SysuiTestCase() { mapper.apply { overrideResource( com.android.internal.R.array.config_foldedDeviceStates, - intArrayOf(1, 2, 3) + intArrayOf(1, 2, 3), ) } whenever(deviceStateManager.supportedDeviceStates).thenReturn(kosmos.foldedDeviceStateList) @@ -176,11 +176,11 @@ class RotationLockTileMapperTest : SysuiTestCase() { private fun createRotationLockTileState( activationState: QSTileState.ActivationState, secondaryLabel: String, - iconRes: Int + iconRes: Int, ): QSTileState { val label = context.getString(R.string.quick_settings_rotation_unlocked_label) return QSTileState( - { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + Icon.Loaded(context.getDrawable(iconRes)!!, null), iconRes, label, activationState, @@ -190,7 +190,7 @@ class RotationLockTileMapperTest : SysuiTestCase() { secondaryLabel, QSTileState.SideViewIcon.None, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt index 9bb61415de28..9a450653aa8f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt @@ -46,7 +46,7 @@ class DataSaverTileMapperTest : SysuiTestCase() { addOverride(R.drawable.qs_data_saver_icon_on, TestStubDrawable()) } .resources, - context.theme + context.theme, ) } @@ -59,7 +59,7 @@ class DataSaverTileMapperTest : SysuiTestCase() { val expectedState = createDataSaverTileState( QSTileState.ActivationState.ACTIVE, - R.drawable.qs_data_saver_icon_on + R.drawable.qs_data_saver_icon_on, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -73,14 +73,14 @@ class DataSaverTileMapperTest : SysuiTestCase() { val expectedState = createDataSaverTileState( QSTileState.ActivationState.INACTIVE, - R.drawable.qs_data_saver_icon_off + R.drawable.qs_data_saver_icon_off, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } private fun createDataSaverTileState( activationState: QSTileState.ActivationState, - iconRes: Int + iconRes: Int, ): QSTileState { val label = context.getString(R.string.data_saver) val secondaryLabel = @@ -91,7 +91,7 @@ class DataSaverTileMapperTest : SysuiTestCase() { else context.resources.getStringArray(R.array.tile_states_saver)[0] return QSTileState( - { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + Icon.Loaded(context.getDrawable(iconRes)!!, null), iconRes, label, activationState, @@ -101,7 +101,7 @@ class DataSaverTileMapperTest : SysuiTestCase() { null, QSTileState.SideViewIcon.None, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt index 336b56612261..cd683c44a59c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt @@ -52,7 +52,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() { addOverride(R.drawable.qs_screen_record_icon_off, TestStubDrawable()) } .resources, - context.theme + context.theme, ) } @@ -82,7 +82,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() { createScreenRecordTileState( QSTileState.ActivationState.ACTIVE, R.drawable.qs_screen_record_icon_on, - String.format("%d...", timeLeft) + String.format("%d...", timeLeft), ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -110,7 +110,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() { val label = context.getString(R.string.quick_settings_screen_record_label) return QSTileState( - { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + Icon.Loaded(context.getDrawable(iconRes)!!, null), iconRes, label, activationState, @@ -123,7 +123,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() { QSTileState.SideViewIcon.Chevron else QSTileState.SideViewIcon.None, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt index b08f39b0accf..c569403960d0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt @@ -56,7 +56,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() { context.getString(R.string.quick_settings_camera_mic_available), R.drawable.qs_camera_access_icon_on, null, - CAMERA + CAMERA, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -74,7 +74,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() { context.getString(R.string.quick_settings_camera_mic_blocked), R.drawable.qs_camera_access_icon_off, null, - CAMERA + CAMERA, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -92,7 +92,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() { context.getString(R.string.quick_settings_camera_mic_available), R.drawable.qs_mic_access_on, null, - MICROPHONE + MICROPHONE, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -110,7 +110,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() { context.getString(R.string.quick_settings_camera_mic_blocked), R.drawable.qs_mic_access_off, null, - MICROPHONE + MICROPHONE, ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @@ -146,7 +146,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() { else context.getString(R.string.quick_settings_mic_label) return QSTileState( - { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + Icon.Loaded(context.getDrawable(iconRes)!!, null), iconRes, label, activationState, @@ -156,7 +156,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() { stateDescription, QSTileState.SideViewIcon.None, QSTileState.EnabledState.ENABLED, - Switch::class.qualifiedName + Switch::class.qualifiedName, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt index c021caa598b9..0d2ebe42b7ad 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt @@ -49,7 +49,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { addOverride(R.drawable.qs_light_dark_theme_icon_on, TestStubDrawable()) } .resources, - context.theme + context.theme, ) } @@ -69,7 +69,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { expandedAccessibilityClass: KClass<out View>? = Switch::class, ): QSTileState { return QSTileState( - { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + Icon.Loaded(context.getDrawable(iconRes)!!, null), iconRes, label, activationState, @@ -79,7 +79,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { stateDescription, sideViewIcon, enabledState, - expandedAccessibilityClass?.qualifiedName + expandedAccessibilityClass?.qualifiedName, ) } @@ -98,7 +98,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { createUiNightModeTileState( activationState = QSTileState.ActivationState.UNAVAILABLE, secondaryLabel = expectedSecondaryLabel, - contentDescription = expectedContentDescription + contentDescription = expectedContentDescription, ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -118,7 +118,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { createUiNightModeTileState( activationState = QSTileState.ActivationState.UNAVAILABLE, secondaryLabel = expectedSecondaryLabel, - contentDescription = expectedContentDescription + contentDescription = expectedContentDescription, ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -136,7 +136,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { activationState = QSTileState.ActivationState.INACTIVE, label = expectedLabel, secondaryLabel = expectedSecondaryLabel, - contentDescription = expectedLabel + contentDescription = expectedLabel, ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -155,7 +155,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { label = expectedLabel, secondaryLabel = expectedSecondaryLabel, activationState = QSTileState.ActivationState.ACTIVE, - contentDescription = expectedLabel + contentDescription = expectedLabel, ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -174,7 +174,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { label = expectedLabel, secondaryLabel = expectedSecondaryLabel, activationState = QSTileState.ActivationState.ACTIVE, - contentDescription = expectedLabel + contentDescription = expectedLabel, ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -193,7 +193,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { label = expectedLabel, secondaryLabel = expectedSecondaryLabel, activationState = QSTileState.ActivationState.INACTIVE, - contentDescription = expectedLabel + contentDescription = expectedLabel, ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -214,7 +214,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { activationState = QSTileState.ActivationState.ACTIVE, contentDescription = expectedLabel, supportedActions = - setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -237,7 +237,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { secondaryLabel = expectedSecondaryLabel, activationState = QSTileState.ActivationState.UNAVAILABLE, contentDescription = expectedContentDescription, - supportedActions = setOf(QSTileState.UserAction.LONG_CLICK) + supportedActions = setOf(QSTileState.UserAction.LONG_CLICK), ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -258,7 +258,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { activationState = QSTileState.ActivationState.INACTIVE, contentDescription = expectedLabel, supportedActions = - setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -279,7 +279,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { secondaryLabel = expectedSecondaryLabel, activationState = QSTileState.ActivationState.UNAVAILABLE, contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel), - supportedActions = setOf(QSTileState.UserAction.LONG_CLICK) + supportedActions = setOf(QSTileState.UserAction.LONG_CLICK), ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -300,7 +300,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { secondaryLabel = expectedSecondaryLabel, activationState = QSTileState.ActivationState.UNAVAILABLE, contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel), - supportedActions = setOf(QSTileState.UserAction.LONG_CLICK) + supportedActions = setOf(QSTileState.UserAction.LONG_CLICK), ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -312,7 +312,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { nightMode = true, powerSave = false, isLocationEnabled = true, - uiMode = UiModeManager.MODE_NIGHT_AUTO + uiMode = UiModeManager.MODE_NIGHT_AUTO, ) val actualState: QSTileState = mapper.map(qsTileConfig, inputModel) @@ -328,7 +328,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { activationState = QSTileState.ActivationState.ACTIVE, contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel), supportedActions = - setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -340,7 +340,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { nightMode = false, powerSave = false, isLocationEnabled = true, - uiMode = UiModeManager.MODE_NIGHT_AUTO + uiMode = UiModeManager.MODE_NIGHT_AUTO, ) val actualState: QSTileState = mapper.map(qsTileConfig, inputModel) @@ -356,7 +356,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { activationState = QSTileState.ActivationState.INACTIVE, contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel), supportedActions = - setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -379,7 +379,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { activationState = QSTileState.ActivationState.ACTIVE, contentDescription = expectedLabel, supportedActions = - setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -401,7 +401,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { activationState = QSTileState.ActivationState.INACTIVE, contentDescription = expectedLabel, supportedActions = - setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -413,7 +413,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { nightMode = false, powerSave = false, uiMode = UiModeManager.MODE_NIGHT_CUSTOM, - nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN + nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, ) val actualState: QSTileState = mapper.map(qsTileConfig, inputModel) @@ -428,7 +428,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { activationState = QSTileState.ActivationState.INACTIVE, contentDescription = expectedLabel, supportedActions = - setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -440,7 +440,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { nightMode = true, powerSave = false, uiMode = UiModeManager.MODE_NIGHT_CUSTOM, - nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN + nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, ) val actualState: QSTileState = mapper.map(qsTileConfig, inputModel) @@ -455,7 +455,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { activationState = QSTileState.ActivationState.ACTIVE, contentDescription = expectedLabel, supportedActions = - setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } @@ -467,7 +467,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { nightMode = false, powerSave = true, uiMode = UiModeManager.MODE_NIGHT_CUSTOM, - nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN + nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, ) val actualState: QSTileState = mapper.map(qsTileConfig, inputModel) @@ -484,7 +484,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { secondaryLabel = expectedSecondaryLabel, activationState = QSTileState.ActivationState.UNAVAILABLE, contentDescription = expectedContentDescription, - supportedActions = setOf(QSTileState.UserAction.LONG_CLICK) + supportedActions = setOf(QSTileState.UserAction.LONG_CLICK), ) QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt index e7bde681fe6f..86321ea04703 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt @@ -56,7 +56,7 @@ class WorkModeTileMapperTest : SysuiTestCase() { whenever( devicePolicyResourceManager.getString( eq(DevicePolicyResources.Strings.SystemUi.QS_WORK_PROFILE_LABEL), - any() + any(), ) ) .thenReturn(testLabel) @@ -66,12 +66,12 @@ class WorkModeTileMapperTest : SysuiTestCase() { .apply { addOverride( com.android.internal.R.drawable.stat_sys_managed_profile_status, - TestStubDrawable() + TestStubDrawable(), ) } .resources, context.theme, - devicePolicyManager + devicePolicyManager, ) } @@ -105,13 +105,11 @@ class WorkModeTileMapperTest : SysuiTestCase() { QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState) } - private fun createWorkModeTileState( - activationState: QSTileState.ActivationState, - ): QSTileState { + private fun createWorkModeTileState(activationState: QSTileState.ActivationState): QSTileState { val label = testLabel val iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status return QSTileState( - icon = { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + icon = Icon.Loaded(context.getDrawable(iconRes)!!, null), iconRes = iconRes, label = label, activationState = activationState, @@ -134,7 +132,7 @@ class WorkModeTileMapperTest : SysuiTestCase() { stateDescription = null, sideViewIcon = QSTileState.SideViewIcon.None, enabledState = QSTileState.EnabledState.ENABLED, - expandedAccessibilityClassName = Switch::class.qualifiedName + expandedAccessibilityClassName = Switch::class.qualifiedName, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt index c33e2a49ef5d..954215eede0d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt @@ -184,10 +184,7 @@ class QSTileViewModelTest : SysuiTestCase() { { object : QSTileDataToStateMapper<String> { override fun map(config: QSTileConfig, data: String): QSTileState = - QSTileState.build( - { Icon.Resource(0, ContentDescription.Resource(0)) }, - data - ) {} + QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), data) {} } }, disabledByPolicyInteractor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt index 7955f2fc1335..0219a4c58dcf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt @@ -104,7 +104,7 @@ class QSTileViewModelUserInputTest : SysuiTestCase() { eq(tileConfig.tileSpec), eq(userAction), any(), - eq("initial_data") + eq("initial_data"), ) verify(qsTileAnalytics).trackUserAction(eq(tileConfig), eq(userAction)) } @@ -130,7 +130,7 @@ class QSTileViewModelUserInputTest : SysuiTestCase() { .logUserActionRejectedByPolicy( eq(userAction), eq(tileConfig.tileSpec), - eq(DISABLED_RESTRICTION) + eq(DISABLED_RESTRICTION), ) verify(qsTileAnalytics, never()).trackUserAction(any(), any()) } @@ -159,7 +159,7 @@ class QSTileViewModelUserInputTest : SysuiTestCase() { .logUserActionRejectedByPolicy( eq(userAction), eq(tileConfig.tileSpec), - eq(DISABLED_RESTRICTION) + eq(DISABLED_RESTRICTION), ) verify(qsTileAnalytics, never()).trackUserAction(any(), any()) } @@ -174,7 +174,7 @@ class QSTileViewModelUserInputTest : SysuiTestCase() { QSTilePolicy.Restricted( listOf( DISABLED_RESTRICTION, - FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2 + FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2, ) ) } @@ -194,13 +194,13 @@ class QSTileViewModelUserInputTest : SysuiTestCase() { .logUserActionRejectedByPolicy( eq(userAction), eq(tileConfig.tileSpec), - eq(DISABLED_RESTRICTION) + eq(DISABLED_RESTRICTION), ) verify(qsTileLogger, never()) .logUserActionRejectedByPolicy( eq(userAction), eq(tileConfig.tileSpec), - eq(FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2) + eq(FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2), ) verify(qsTileAnalytics, never()).trackUserAction(any(), any()) } @@ -243,10 +243,7 @@ class QSTileViewModelUserInputTest : SysuiTestCase() { { object : QSTileDataToStateMapper<String> { override fun map(config: QSTileConfig, data: String): QSTileState = - QSTileState.build( - { Icon.Resource(0, ContentDescription.Resource(0)) }, - data - ) {} + QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), data) {} } }, disabledByPolicyInteractor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 319f1e577cd9..3be8a380b191 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -44,6 +44,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope @@ -153,7 +154,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() = kosmos.runTest { - val actions by testScope.collectLastValue(kosmos.lockscreenUserActionsViewModel.actions) + val actions by collectLastValue(kosmos.lockscreenUserActionsViewModel.actions) val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) @@ -170,7 +171,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { kosmos.runTest { setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) - val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions) + val actions by collectLastValue(lockscreenUserActionsViewModel.actions) val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone) @@ -180,7 +181,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = kosmos.runTest { - val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions) + val actions by collectLastValue(shadeUserActionsViewModel.actions) setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) assertCurrentScene(Scenes.Lockscreen) @@ -197,8 +198,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() = kosmos.runTest { - val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions) - val canSwipeToEnter by testScope.collectLastValue(deviceEntryInteractor.canSwipeToEnter) + val actions by collectLastValue(shadeUserActionsViewModel.actions) + val canSwipeToEnter by collectLastValue(deviceEntryInteractor.canSwipeToEnter) setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) @@ -279,7 +280,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() = kosmos.runTest { unlockDevice() - val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions) + val actions by collectLastValue(lockscreenUserActionsViewModel.actions) val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone) @@ -302,7 +303,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() = kosmos.runTest { setAuthMethod(AuthenticationMethodModel.Password) - val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions) + val actions by collectLastValue(lockscreenUserActionsViewModel.actions) val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) @@ -319,14 +320,13 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun bouncerActionButtonClick_opensEmergencyServicesDialer() = kosmos.runTest { setAuthMethod(AuthenticationMethodModel.Password) - val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions) + val actions by collectLastValue(lockscreenUserActionsViewModel.actions) val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition(to = upDestinationSceneKey) - val bouncerActionButton by - testScope.collectLastValue(bouncerSceneContentViewModel.actionButton) + val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton) assertWithMessage("Bouncer action button not visible") .that(bouncerActionButton) .isNotNull() @@ -341,14 +341,13 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { kosmos.runTest { setAuthMethod(AuthenticationMethodModel.Password) startPhoneCall() - val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions) + val actions by collectLastValue(lockscreenUserActionsViewModel.actions) val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition(to = upDestinationSceneKey) - val bouncerActionButton by - testScope.collectLastValue(bouncerSceneContentViewModel.actionButton) + val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton) assertWithMessage("Bouncer action button not visible during call") .that(bouncerActionButton) .isNotNull() @@ -574,7 +573,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(getCurrentSceneInUi()) .isEqualTo(Scenes.Bouncer) val authMethodViewModel by - testScope.collectLastValue(bouncerSceneContentViewModel.authMethodViewModel) + collectLastValue(bouncerSceneContentViewModel.authMethodViewModel) assertWithMessage("Cannot enter PIN when not using a PIN authentication method!") .that(authMethodViewModel) .isInstanceOf(PinBouncerViewModel::class.java) @@ -603,7 +602,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(getCurrentSceneInUi()) .isEqualTo(Scenes.Bouncer) val authMethodViewModel by - testScope.collectLastValue(bouncerSceneContentViewModel.authMethodViewModel) + collectLastValue(bouncerSceneContentViewModel.authMethodViewModel) assertWithMessage("Cannot enter PIN when not using a PIN authentication method!") .that(authMethodViewModel) .isInstanceOf(PinBouncerViewModel::class.java) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt index bfe5ef7acbce..db2297c99315 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt @@ -32,7 +32,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.statusbar.NotificationPresenter -import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.init.NotificationsController @@ -69,7 +69,7 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { private val notificationPresenter = mock<NotificationPresenter>() private val notificationsController = mock<NotificationsController>() private val powerInteractor = PowerInteractorFactory.create().powerInteractor - private val activeNotificationsRepository = ActiveNotificationListRepository() + private val activeNotificationsRepository = kosmos.activeNotificationListRepository private val activeNotificationsInteractor = ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher) 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/screenshot/data/model/DisplayContentScenarios.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt index 4d71dc45001d..4871564a5376 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt @@ -18,6 +18,8 @@ package com.android.systemui.screenshot.data.model import android.content.ComponentName import android.graphics.Rect +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_FULL_SCREEN +import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_MAXIMIZED import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREE_FORM import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FULL_SCREEN import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.PIP @@ -153,11 +155,23 @@ object DisplayContentScenarios { fun freeFormApps( vararg tasks: TaskSpec, focusedTaskId: Int, + maximizedTaskId: Int = -1, shadeExpanded: Boolean = false, ): DisplayContentModel { val freeFormTasks = tasks - .map { freeForm(it) } + .map { + freeForm( + task = it, + bounds = + if (it.taskId == maximizedTaskId) { + FREEFORM_MAXIMIZED + } else { + FREE_FORM + }, + maxBounds = FREEFORM_FULL_SCREEN, + ) + } // Root tasks are ordered top-down in List<RootTaskInfo>. // Sort 'focusedTaskId' last (Boolean natural ordering: [false, true]) .sortedBy { it.childTaskIds[0] != focusedTaskId } @@ -180,9 +194,9 @@ object DisplayContentScenarios { val PIP = Rect(440, 1458, 1038, 1794) val SPLIT_TOP = Rect(0, 0, 1080, 1187) val SPLIT_BOTTOM = Rect(0, 1213, 1080, 2400) - val FREE_FORM = Rect(119, 332, 1000, 1367) // "Tablet" size + val FREE_FORM = Rect(119, 332, 1000, 1367) val FREEFORM_FULL_SCREEN = Rect(0, 0, 2560, 1600) val FREEFORM_MAXIMIZED = Rect(0, 48, 2560, 1480) val FREEFORM_SPLIT_LEFT = Rect(0, 0, 1270, 1600) @@ -301,11 +315,12 @@ object DisplayContentScenarios { } /** An activity in FreeForm mode */ - fun freeForm(task: TaskSpec, bounds: Rect = FREE_FORM) = + fun freeForm(task: TaskSpec, bounds: Rect = FREE_FORM, maxBounds: Rect = bounds) = newRootTaskInfo( taskId = task.taskId, userId = task.userId, bounds = bounds, + maxBounds = maxBounds, windowingMode = WindowingMode.Freeform, topActivity = ComponentName.unflattenFromString(task.name), ) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt index cedf0c8a2c06..4f6871ebbbf4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt @@ -84,6 +84,7 @@ fun newRootTaskInfo( activityType: ActivityType = Standard, windowingMode: WindowingMode = FullScreen, bounds: Rect = Rect(), + maxBounds: Rect = bounds, topActivity: ComponentName? = null, topActivityType: ActivityType = Standard, numActivities: Int? = null, @@ -94,6 +95,7 @@ fun newRootTaskInfo( setWindowingMode(windowingMode.toInt()) setActivityType(activityType.toInt()) setBounds(bounds) + setMaxBounds(maxBounds) } this.bounds = bounds this.displayId = displayId diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt index b7f565df4a3c..c884b9a9ac54 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt @@ -90,9 +90,11 @@ class PrivateProfilePolicyTest { Matched( PrivateProfilePolicy.NAME, PrivateProfilePolicy.PRIVATE_TASK_VISIBLE, - CaptureParameters( + LegacyCaptureParameters( type = FullScreen(displayId = 0), - component = ComponentName.unflattenFromString(YOUTUBE), + component = + ComponentName.unflattenFromString(YOUTUBE) + ?: error("Invalid component name"), owner = UserHandle.of(PRIVATE), ), ) @@ -142,7 +144,7 @@ class PrivateProfilePolicyTest { Matched( PrivateProfilePolicy.NAME, PrivateProfilePolicy.PRIVATE_TASK_VISIBLE, - CaptureParameters( + LegacyCaptureParameters( type = FullScreen(displayId = 0), component = ComponentName.unflattenFromString(YOUTUBE), owner = UserHandle.of(PRIVATE), @@ -167,7 +169,7 @@ class PrivateProfilePolicyTest { Matched( PrivateProfilePolicy.NAME, PrivateProfilePolicy.PRIVATE_TASK_VISIBLE, - CaptureParameters( + LegacyCaptureParameters( type = FullScreen(displayId = 0), component = ComponentName.unflattenFromString(FILES), owner = UserHandle.of(PRIVATE), @@ -188,7 +190,7 @@ class PrivateProfilePolicyTest { Matched( PrivateProfilePolicy.NAME, PrivateProfilePolicy.PRIVATE_TASK_VISIBLE, - CaptureParameters( + LegacyCaptureParameters( type = FullScreen(displayId = 0), component = ComponentName.unflattenFromString(YOUTUBE_PIP), owner = UserHandle.of(PRIVATE), @@ -212,7 +214,7 @@ class PrivateProfilePolicyTest { Matched( PrivateProfilePolicy.NAME, PrivateProfilePolicy.PRIVATE_TASK_VISIBLE, - CaptureParameters( + LegacyCaptureParameters( type = FullScreen(displayId = 0), component = ComponentName.unflattenFromString(YOUTUBE_PIP), owner = UserHandle.of(PRIVATE), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt index 28eb9fc1364b..948c24ec8aa2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt @@ -17,11 +17,11 @@ package com.android.systemui.screenshot.policy import android.content.ComponentName +import android.graphics.Rect import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.systemui.kosmos.Kosmos import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES -import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.LAUNCHER import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.MESSAGES import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_FULL_SCREEN @@ -32,10 +32,10 @@ import com.android.systemui.screenshot.data.model.DisplayContentScenarios.freeFo import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps +import com.android.systemui.screenshot.data.model.allTasks import com.android.systemui.screenshot.data.repository.profileTypeRepository import com.android.systemui.screenshot.policy.CaptureType.FullScreen import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask -import com.android.systemui.screenshot.policy.CaptureType.RootTask import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL import com.android.systemui.screenshot.policy.TestUserIds.PRIVATE import com.android.systemui.screenshot.policy.TestUserIds.WORK @@ -50,69 +50,81 @@ class ScreenshotPolicyTest { private val defaultComponent = ComponentName("default", "default") private val defaultOwner = UserHandle.SYSTEM + private val policy = ScreenshotPolicy(kosmos.profileTypeRepository) @Test fun fullScreen_work() = runTest { - val policy = ScreenshotPolicy(kosmos.profileTypeRepository) + val displayContent = singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK)) + val expectedFocusedTask = + displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 } - val result = - policy.apply( - singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK)), - defaultComponent, - defaultOwner, - ) + val result = policy.apply(displayContent, defaultComponent, defaultOwner) assertThat(result) .isEqualTo( CaptureParameters( type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN), - component = ComponentName.unflattenFromString(FILES), - owner = UserHandle.of(WORK), + contentTask = + TaskReference( + taskId = expectedFocusedTask.id, + component = expectedFocusedTask.componentName, + owner = UserHandle.of(expectedFocusedTask.userId), + bounds = expectedFocusedTask.bounds, + ), + owner = UserHandle.of(expectedFocusedTask.userId), ) ) } @Test fun fullScreen_private() = runTest { - val policy = ScreenshotPolicy(kosmos.profileTypeRepository) + val displayContent = + singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE)) + val expectedFocusedTask = + displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 } - val result = - policy.apply( - singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE)), - defaultComponent, - defaultOwner, - ) + val result = policy.apply(displayContent, defaultComponent, defaultOwner) assertThat(result) .isEqualTo( CaptureParameters( type = FullScreen(displayId = 0), - component = ComponentName.unflattenFromString(YOUTUBE), - owner = UserHandle.of(PRIVATE), + contentTask = + TaskReference( + taskId = expectedFocusedTask.id, + component = expectedFocusedTask.componentName, + owner = UserHandle.of(expectedFocusedTask.userId), + bounds = expectedFocusedTask.bounds, + ), + owner = UserHandle.of(expectedFocusedTask.userId), ) ) } @Test fun splitScreen_workAndPersonal() = runTest { - val policy = ScreenshotPolicy(kosmos.profileTypeRepository) - - val result = - policy.apply( - splitScreenApps( - first = TaskSpec(taskId = 1002, name = FILES, userId = WORK), - second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL), - focusedTaskId = 1002, - ), - defaultComponent, - defaultOwner, + val displayContent = + splitScreenApps( + first = TaskSpec(taskId = 1002, name = FILES, userId = WORK), + second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL), + focusedTaskId = 1002, ) + val expectedFocusedTask = + displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 } + + val result = policy.apply(displayContent, defaultComponent, defaultOwner) assertThat(result) .isEqualTo( CaptureParameters( type = FullScreen(displayId = 0), - component = ComponentName.unflattenFromString(YOUTUBE), + contentTask = + TaskReference( + taskId = expectedFocusedTask.id, + component = expectedFocusedTask.componentName, + owner = UserHandle.of(expectedFocusedTask.userId), + bounds = expectedFocusedTask.bounds, + ), owner = UserHandle.of(PERSONAL), ) ) @@ -120,24 +132,28 @@ class ScreenshotPolicyTest { @Test fun splitScreen_personalAndPrivate() = runTest { - val policy = ScreenshotPolicy(kosmos.profileTypeRepository) - - val result = - policy.apply( - splitScreenApps( - first = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL), - second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE), - focusedTaskId = 1002, - ), - defaultComponent, - defaultOwner, + val displayContent = + splitScreenApps( + first = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL), + second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE), + focusedTaskId = 1002, ) + val expectedFocusedTask = + displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 } + + val result = policy.apply(displayContent, defaultComponent, defaultOwner) assertThat(result) .isEqualTo( CaptureParameters( type = FullScreen(displayId = 0), - component = ComponentName.unflattenFromString(YOUTUBE), + contentTask = + TaskReference( + taskId = expectedFocusedTask.id, + component = expectedFocusedTask.componentName, + owner = UserHandle.of(expectedFocusedTask.userId), + bounds = expectedFocusedTask.bounds, + ), owner = UserHandle.of(PRIVATE), ) ) @@ -145,24 +161,28 @@ class ScreenshotPolicyTest { @Test fun splitScreen_workAndPrivate() = runTest { - val policy = ScreenshotPolicy(kosmos.profileTypeRepository) - - val result = - policy.apply( - splitScreenApps( - first = TaskSpec(taskId = 1002, name = FILES, userId = WORK), - second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE), - focusedTaskId = 1002, - ), - defaultComponent, - defaultOwner, + val displayContent = + splitScreenApps( + first = TaskSpec(taskId = 1002, name = FILES, userId = WORK), + second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE), + focusedTaskId = 1002, ) + val expectedFocusedTask = + displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 } + + val result = policy.apply(displayContent, defaultComponent, defaultOwner) assertThat(result) .isEqualTo( CaptureParameters( type = FullScreen(displayId = 0), - component = ComponentName.unflattenFromString(YOUTUBE), + contentTask = + TaskReference( + taskId = expectedFocusedTask.id, + component = expectedFocusedTask.componentName, + owner = UserHandle.of(expectedFocusedTask.userId), + bounds = expectedFocusedTask.bounds, + ), owner = UserHandle.of(PRIVATE), ) ) @@ -170,32 +190,31 @@ class ScreenshotPolicyTest { @Test fun splitScreen_twoWorkTasks() = runTest { - val policy = ScreenshotPolicy(kosmos.profileTypeRepository) - - val result = - policy.apply( - splitScreenApps( - parentTaskId = 1, - parentBounds = FREEFORM_FULL_SCREEN, - orientation = VERTICAL, - first = TaskSpec(taskId = 1002, name = FILES, userId = WORK), - second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = WORK), - focusedTaskId = 1002, - ), - defaultComponent, - defaultOwner, + val displayContent = + splitScreenApps( + parentTaskId = 1, + parentBounds = FREEFORM_FULL_SCREEN, + orientation = VERTICAL, + first = TaskSpec(taskId = 1002, name = FILES, userId = WORK), + second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = WORK), + focusedTaskId = 1002, ) + val expectedFocusedTask = + displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 } + + val result = policy.apply(displayContent, defaultComponent, defaultOwner) assertThat(result) .isEqualTo( CaptureParameters( - type = - RootTask( - parentTaskId = 1, - taskBounds = FREEFORM_FULL_SCREEN, - childTaskIds = listOf(1002, 1003), + type = IsolatedTask(taskBounds = FREEFORM_FULL_SCREEN, taskId = 1), + contentTask = + TaskReference( + taskId = expectedFocusedTask.id, + component = expectedFocusedTask.componentName, + owner = UserHandle.of(expectedFocusedTask.userId), + bounds = expectedFocusedTask.bounds, ), - component = ComponentName.unflattenFromString(FILES), owner = UserHandle.of(WORK), ) ) @@ -203,99 +222,112 @@ class ScreenshotPolicyTest { @Test fun freeform_floatingWindows() = runTest { - val policy = ScreenshotPolicy(kosmos.profileTypeRepository) - - val result = - policy.apply( - freeFormApps( - TaskSpec(taskId = 1002, name = FILES, userId = WORK), - TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL), - focusedTaskId = 1003, - ), - defaultComponent, - defaultOwner, + val displayContent = + freeFormApps( + TaskSpec(taskId = 1002, name = FILES, userId = WORK), + TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL), + focusedTaskId = 1003, ) + val expectedFocusedTask = + displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1003 } + + val result = policy.apply(displayContent, defaultComponent, defaultOwner) assertThat(result) .isEqualTo( CaptureParameters( type = FullScreen(displayId = 0), - component = ComponentName.unflattenFromString(YOUTUBE), + contentTask = + TaskReference( + taskId = expectedFocusedTask.id, + component = expectedFocusedTask.componentName, + owner = UserHandle.of(expectedFocusedTask.userId), + bounds = expectedFocusedTask.bounds, + ), owner = UserHandle.of(PERSONAL), ) ) } @Test - fun freeform_floatingWindows_maximized() = runTest { - val policy = ScreenshotPolicy(kosmos.profileTypeRepository) - - val result = - policy.apply( - freeFormApps( - TaskSpec(taskId = 1002, name = FILES, userId = WORK), - TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL), - focusedTaskId = 1003, - ), - defaultComponent, - defaultOwner, + fun freeform_floatingWindows_work_maximized() = runTest { + val displayContent = + freeFormApps( + TaskSpec(taskId = 1002, name = FILES, userId = WORK), + TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL), + focusedTaskId = 1002, + maximizedTaskId = 1002, ) + val expectedFocusedTask = + displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 } + + val result = policy.apply(displayContent, defaultComponent, defaultOwner) assertThat(result) .isEqualTo( CaptureParameters( - type = FullScreen(displayId = 0), - component = ComponentName.unflattenFromString(YOUTUBE), - owner = UserHandle.of(PERSONAL), + type = IsolatedTask(taskId = 1002, taskBounds = expectedFocusedTask.bounds), + contentTask = + TaskReference( + taskId = expectedFocusedTask.id, + component = expectedFocusedTask.componentName, + owner = UserHandle.of(expectedFocusedTask.userId), + bounds = expectedFocusedTask.bounds, + ), + owner = UserHandle.of(WORK), ) ) } @Test fun freeform_floatingWindows_withPrivate() = runTest { - val policy = ScreenshotPolicy(kosmos.profileTypeRepository) - - val result = - policy.apply( - freeFormApps( - TaskSpec(taskId = 1002, name = FILES, userId = WORK), - TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE), - TaskSpec(taskId = 1004, name = MESSAGES, userId = PERSONAL), - focusedTaskId = 1004, - ), - defaultComponent, - defaultOwner, + val displayContent = + freeFormApps( + TaskSpec(taskId = 1002, name = FILES, userId = WORK), + TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE), + TaskSpec(taskId = 1004, name = MESSAGES, userId = PERSONAL), + focusedTaskId = 1004, ) + val expectedFocusedTask = displayContent.allTasks().single { it.id == 1004 } + + val result = policy.apply(displayContent, defaultComponent, defaultOwner) assertThat(result) .isEqualTo( CaptureParameters( type = FullScreen(displayId = 0), - component = ComponentName.unflattenFromString(YOUTUBE), + contentTask = + TaskReference( + taskId = expectedFocusedTask.id, + component = expectedFocusedTask.componentName, + owner = UserHandle.of(expectedFocusedTask.userId), + bounds = expectedFocusedTask.bounds, + ), owner = UserHandle.of(PRIVATE), ) ) } @Test - fun freeform_floating_workOnly() = runTest { - val policy = ScreenshotPolicy(kosmos.profileTypeRepository) - - val result = - policy.apply( - freeFormApps( - TaskSpec(taskId = 1002, name = FILES, userId = WORK), - focusedTaskId = 1002, - ), - defaultComponent, - defaultOwner, - ) + fun freeform_floating_work() = runTest { + val displayContent = + freeFormApps(TaskSpec(taskId = 1002, name = FILES, userId = WORK), focusedTaskId = 1002) + val expectedFocusedTask = + displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 } + + val result = policy.apply(displayContent, defaultComponent, defaultOwner) assertThat(result) .isEqualTo( CaptureParameters( type = FullScreen(displayId = 0), - component = ComponentName.unflattenFromString(LAUNCHER), + contentTask = + TaskReference( + taskId = expectedFocusedTask.id, + component = expectedFocusedTask.componentName, + owner = UserHandle.of(expectedFocusedTask.userId), + bounds = expectedFocusedTask.bounds, + ), owner = defaultOwner, ) ) @@ -303,23 +335,27 @@ class ScreenshotPolicyTest { @Test fun fullScreen_shadeExpanded() = runTest { - val policy = ScreenshotPolicy(kosmos.profileTypeRepository) - - val result = - policy.apply( - singleFullScreen( - TaskSpec(taskId = 1002, name = FILES, userId = WORK), - shadeExpanded = true, - ), - defaultComponent, - defaultOwner, + val displayContent = + singleFullScreen( + TaskSpec(taskId = 1002, name = FILES, userId = WORK), + shadeExpanded = true, ) + val expectedFocusedTask = + displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 } + + val result = policy.apply(displayContent, defaultComponent, defaultOwner) assertThat(result) .isEqualTo( CaptureParameters( type = FullScreen(displayId = 0), - component = defaultComponent, + contentTask = + TaskReference( + taskId = -1, + component = defaultComponent, + owner = defaultOwner, + bounds = Rect(), + ), owner = defaultOwner, ) ) @@ -327,25 +363,55 @@ class ScreenshotPolicyTest { @Test fun fullScreen_with_PictureInPicture() = runTest { - val policy = ScreenshotPolicy(kosmos.profileTypeRepository) - - val result = - policy.apply( - pictureInPictureApp( - pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL), - fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK), - ), - defaultComponent, - defaultOwner, + val displayContent = + pictureInPictureApp( + pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL), + fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK), ) + val expectedFocusedTask = displayContent.allTasks().single { it.id == 1003 } + + val result = policy.apply(displayContent, defaultComponent, defaultOwner) assertThat(result) .isEqualTo( CaptureParameters( type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN), - component = ComponentName.unflattenFromString(FILES), + contentTask = + TaskReference( + taskId = expectedFocusedTask.id, + component = expectedFocusedTask.componentName, + owner = UserHandle.of(expectedFocusedTask.userId), + bounds = expectedFocusedTask.bounds, + ), owner = UserHandle.of(WORK), ) ) } + + // TODO: PiP tasks should affect ownership (e.g. Private) + @Test + fun fullScreen_with_PictureInPicture_private() = runTest { + val displayContent = + pictureInPictureApp( + pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE), + fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = PERSONAL), + ) + val expectedFocusedTask = displayContent.allTasks().single { it.id == 1003 } + + val result = policy.apply(displayContent, defaultComponent, defaultOwner) + assertThat(result) + .isEqualTo( + CaptureParameters( + type = FullScreen(displayId = 0), + contentTask = + TaskReference( + taskId = expectedFocusedTask.id, + component = expectedFocusedTask.componentName, + owner = UserHandle.of(expectedFocusedTask.userId), + bounds = expectedFocusedTask.bounds, + ), + owner = UserHandle.of(PRIVATE), + ) + ) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt index 30a786c291cb..c1477fe52f0e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt @@ -135,7 +135,7 @@ class WorkProfilePolicyTest { PolicyResult.Matched( policy = WorkProfilePolicy.NAME, reason = WORK_TASK_IS_TOP, - CaptureParameters( + LegacyCaptureParameters( type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN), component = ComponentName.unflattenFromString(FILES), owner = UserHandle.of(WORK), @@ -162,7 +162,7 @@ class WorkProfilePolicyTest { PolicyResult.Matched( policy = WorkProfilePolicy.NAME, reason = WORK_TASK_IS_TOP, - CaptureParameters( + LegacyCaptureParameters( type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN.splitTop(20)), component = ComponentName.unflattenFromString(FILES), owner = UserHandle.of(WORK), @@ -200,7 +200,7 @@ class WorkProfilePolicyTest { PolicyResult.Matched( policy = WorkProfilePolicy.NAME, reason = WORK_TASK_IS_TOP, - CaptureParameters( + LegacyCaptureParameters( type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN), component = ComponentName.unflattenFromString(FILES), owner = UserHandle.of(WORK), @@ -226,7 +226,7 @@ class WorkProfilePolicyTest { PolicyResult.Matched( policy = WorkProfilePolicy.NAME, reason = WORK_TASK_IS_TOP, - CaptureParameters( + LegacyCaptureParameters( type = IsolatedTask(taskId = 1003, taskBounds = FREE_FORM), component = ComponentName.unflattenFromString(FILES), owner = UserHandle.of(WORK), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java index 2e759a363e20..443595dbdf77 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java @@ -17,7 +17,6 @@ package com.android.systemui.shade; import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; -import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; @@ -63,8 +62,6 @@ import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository; -import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository; -import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.DozeParameters; @@ -159,8 +156,6 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { protected SysuiStatusBarStateController mStatusBarStateController; protected ShadeInteractor mShadeInteractor; - protected ActiveNotificationsInteractor mActiveNotificationsInteractor; - protected Handler mMainHandler; protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback; @@ -204,11 +199,6 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { ), mKosmos.getShadeModeInteractor()); - mActiveNotificationsInteractor = new ActiveNotificationsInteractor( - new ActiveNotificationListRepository(), - StandardTestDispatcher(/* scheduler = */ null, /* name = */ null) - ); - KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); keyguardStatusView.setId(R.id.keyguard_status_view); @@ -277,7 +267,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { mock(DeviceEntryFaceAuthInteractor.class), mShadeRepository, mShadeInteractor, - mActiveNotificationsInteractor, + mKosmos.getActiveNotificationsInteractor(), new JavaAdapter(mTestScope.getBackgroundScope()), mCastController, splitShadeStateController, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index 943fb62003cb..0f476d0a0455 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -35,8 +35,7 @@ import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInte import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationShadeWindowController -import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository -import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.row.NotificationGutsManager import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.DeviceProvisionedController @@ -50,7 +49,6 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import dagger.Lazy -import kotlinx.coroutines.test.StandardTestDispatcher import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -65,8 +63,6 @@ import org.mockito.MockitoAnnotations @SmallTest class ShadeControllerImplTest : SysuiTestCase() { private val executor = FakeExecutor(FakeSystemClock()) - private val testDispatcher = StandardTestDispatcher() - private val activeNotificationsRepository = ActiveNotificationListRepository() private val kosmos = Kosmos() private val testScope = kosmos.testScope @@ -95,7 +91,7 @@ class ShadeControllerImplTest : SysuiTestCase() { FakeKeyguardRepository(), headsUpManager, PowerInteractorFactory.create().powerInteractor, - ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher), + kosmos.activeNotificationsInteractor, kosmos::sceneInteractor, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt index ce9b3beeec9e..7842d75db6c4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.plugins.BcSmartspaceConfigPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView import com.android.systemui.plugins.FalsingManager +import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.Execution import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.withArgCaptor @@ -52,6 +53,10 @@ import org.mockito.kotlin.never @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class CommunalSmartspaceControllerTest : SysuiTestCase() { + @Mock private lateinit var userTracker: UserTracker + + @Mock private lateinit var userContextPrimary: Context + @Mock private lateinit var smartspaceManager: SmartspaceManager @Mock private lateinit var execution: Execution @@ -113,15 +118,18 @@ class CommunalSmartspaceControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session) + `when`(userTracker.userContext).thenReturn(userContextPrimary) + `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java)) + .thenReturn(smartspaceManager) + controller = CommunalSmartspaceController( - context, - smartspaceManager, + userTracker, execution, uiExecutor, precondition, Optional.of(targetFilter), - Optional.of(plugin) + Optional.of(plugin), ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt index e774aed2045b..c83c82dc0914 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.plugins.BcSmartspaceConfigPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView import com.android.systemui.plugins.FalsingManager +import com.android.systemui.settings.UserTracker import com.android.systemui.smartspace.dagger.SmartspaceViewComponent import com.android.systemui.util.concurrency.Execution import com.android.systemui.util.mockito.any @@ -46,66 +47,51 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.anyInt import org.mockito.Mockito.verify import org.mockito.Mockito.`when` -import org.mockito.Mockito.anyInt import org.mockito.MockitoAnnotations -import org.mockito.Spy @SmallTest @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class DreamSmartspaceControllerTest : SysuiTestCase() { - @Mock - private lateinit var smartspaceManager: SmartspaceManager + @Mock private lateinit var userTracker: UserTracker - @Mock - private lateinit var execution: Execution + @Mock private lateinit var userContextPrimary: Context - @Mock - private lateinit var uiExecutor: Executor + @Mock private lateinit var smartspaceManager: SmartspaceManager - @Mock - private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory + @Mock private lateinit var execution: Execution - @Mock - private lateinit var viewComponent: SmartspaceViewComponent + @Mock private lateinit var uiExecutor: Executor - @Mock - private lateinit var weatherViewComponent: SmartspaceViewComponent + @Mock private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory - private val weatherSmartspaceView: SmartspaceView by lazy { - Mockito.spy(TestView(context)) - } + @Mock private lateinit var viewComponent: SmartspaceViewComponent - @Mock - private lateinit var targetFilter: SmartspaceTargetFilter + @Mock private lateinit var weatherViewComponent: SmartspaceViewComponent - @Mock - private lateinit var plugin: BcSmartspaceDataPlugin + private val weatherSmartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) } - @Mock - private lateinit var weatherPlugin: BcSmartspaceDataPlugin + @Mock private lateinit var targetFilter: SmartspaceTargetFilter - @Mock - private lateinit var precondition: SmartspacePrecondition + @Mock private lateinit var plugin: BcSmartspaceDataPlugin - private val smartspaceView: SmartspaceView by lazy { - Mockito.spy(TestView(context)) - } + @Mock private lateinit var weatherPlugin: BcSmartspaceDataPlugin + + @Mock private lateinit var precondition: SmartspacePrecondition - @Mock - private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener + private val smartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) } - @Mock - private lateinit var session: SmartspaceSession + @Mock private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener + + @Mock private lateinit var session: SmartspaceSession private lateinit var controller: DreamSmartspaceController // TODO(b/272811280): Remove usage of real view - private val fakeParent by lazy { - FrameLayout(context) - } + private val fakeParent by lazy { FrameLayout(context) } /** * A class which implements SmartspaceView and extends View. This is mocked to provide the right @@ -134,30 +120,44 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { override fun setMediaTarget(target: SmartspaceTarget?) {} - override fun getSelectedPage(): Int { return 0; } + override fun getSelectedPage(): Int { + return 0 + } - override fun getCurrentCardTopPadding(): Int { return 0; } + override fun getCurrentCardTopPadding(): Int { + return 0 + } } @Before fun setup() { MockitoAnnotations.initMocks(this) `when`(viewComponentFactory.create(any(), eq(plugin), any(), eq(null))) - .thenReturn(viewComponent) + .thenReturn(viewComponent) `when`(viewComponent.getView()).thenReturn(smartspaceView) `when`(viewComponentFactory.create(any(), eq(weatherPlugin), any(), any())) .thenReturn(weatherViewComponent) `when`(weatherViewComponent.getView()).thenReturn(weatherSmartspaceView) `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session) - controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor, - viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin), - Optional.of(weatherPlugin)) + `when`(userTracker.userContext).thenReturn(userContextPrimary) + `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java)) + .thenReturn(smartspaceManager) + + controller = + DreamSmartspaceController( + userTracker, + execution, + uiExecutor, + viewComponentFactory, + precondition, + Optional.of(targetFilter), + Optional.of(plugin), + Optional.of(weatherPlugin), + ) } - /** - * Ensures smartspace session begins on a listener only flow. - */ + /** Ensures smartspace session begins on a listener only flow. */ @Test fun testConnectOnListen() { `when`(precondition.conditionsMet()).thenReturn(true) @@ -165,18 +165,18 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { verify(smartspaceManager).createSmartspaceSession(any()) - var targetListener = withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> { - verify(session).addOnTargetsAvailableListener(any(), capture()) - } + var targetListener = + withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> { + verify(session).addOnTargetsAvailableListener(any(), capture()) + } `when`(targetFilter.filterSmartspaceTarget(any())).thenReturn(true) var target = Mockito.mock(SmartspaceTarget::class.java) targetListener.onTargetsAvailable(listOf(target)) - var targets = withArgCaptor<List<SmartspaceTarget>> { - verify(plugin).onTargetsAvailable(capture()) - } + var targets = + withArgCaptor<List<SmartspaceTarget>> { verify(plugin).onTargetsAvailable(capture()) } assertThat(targets.contains(target)).isTrue() @@ -185,17 +185,16 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { verify(session).close() } - /** - * Ensures session begins when a view is attached. - */ + /** Ensures session begins when a view is attached. */ @Test fun testConnectOnViewCreate() { `when`(precondition.conditionsMet()).thenReturn(true) controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java)) - val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> { - verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null)) - } + val stateChangeListener = + withArgCaptor<View.OnAttachStateChangeListener> { + verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null)) + } val mockView = Mockito.mock(TestView::class.java) `when`(precondition.conditionsMet()).thenReturn(true) @@ -209,9 +208,7 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { verify(session).close() } - /** - * Ensures session is created when weather smartspace view is created and attached. - */ + /** Ensures session is created when weather smartspace view is created and attached. */ @Test fun testConnectOnWeatherViewCreate() { `when`(precondition.conditionsMet()).thenReturn(true) @@ -223,8 +220,8 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { // Then weather view is created with custom view and the default weatherPlugin.getView // should not be called - verify(viewComponentFactory).create(eq(fakeParent), eq(weatherPlugin), any(), - eq(customView)) + verify(viewComponentFactory) + .create(eq(fakeParent), eq(weatherPlugin), any(), eq(customView)) verify(weatherPlugin, Mockito.never()).getView(fakeParent) // And then session is created @@ -234,9 +231,7 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { verify(weatherSmartspaceView).setDozeAmount(0f) } - /** - * Ensures weather plugin registers target listener when it is added from the controller. - */ + /** Ensures weather plugin registers target listener when it is added from the controller. */ @Test fun testAddListenerInController_registersListenerForWeatherPlugin() { val customView = Mockito.mock(TestView::class.java) 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 6e190965d08b..32f4164d509f 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 @@ -66,18 +66,34 @@ class NotifChipsViewModelTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.chips) - setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = null))) + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = null, + isPromoted = true, + ) + ) + ) assertThat(latest).isEmpty() } @Test - fun chips_oneNotif_statusBarIconViewMatches() = + fun chips_onePromotedNotif_statusBarIconViewMatches() = testScope.runTest { val latest by collectLastValue(underTest.chips) val icon = mock<StatusBarIconView>() - setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon))) + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = icon, + isPromoted = true, + ) + ) + ) assertThat(latest).hasSize(1) val chip = latest!![0] @@ -86,7 +102,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test - fun chips_twoNotifs_twoChips() = + fun chips_onlyForPromotedNotifs() = testScope.runTest { val latest by collectLastValue(underTest.chips) @@ -94,8 +110,21 @@ class NotifChipsViewModelTest : SysuiTestCase() { val secondIcon = mock<StatusBarIconView>() setNotifs( listOf( - activeNotificationModel(key = "notif1", statusBarChipIcon = firstIcon), - activeNotificationModel(key = "notif2", statusBarChipIcon = secondIcon), + activeNotificationModel( + key = "notif1", + statusBarChipIcon = firstIcon, + isPromoted = true, + ), + activeNotificationModel( + key = "notif2", + statusBarChipIcon = secondIcon, + isPromoted = true, + ), + activeNotificationModel( + key = "notif3", + statusBarChipIcon = mock<StatusBarIconView>(), + isPromoted = false, + ), ) ) @@ -118,6 +147,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { activeNotificationModel( key = "clickTest", statusBarChipIcon = mock<StatusBarIconView>(), + isPromoted = true, ) ) ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt index b12d7c57e1fd..25d5ce50e03f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt @@ -293,19 +293,27 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { } @Test - fun chips_singleNotifChip_primaryIsNotifSecondaryIsHidden() = + fun chips_singlePromotedNotif_primaryIsNotifSecondaryIsHidden() = testScope.runTest { val latest by collectLastValue(underTest.chips) val icon = mock<StatusBarIconView>() - setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon))) + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = icon, + isPromoted = true, + ) + ) + ) assertIsNotifChip(latest!!.primary, icon) assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } @Test - fun chips_twoNotifChips_primaryAndSecondaryAreNotifsInOrder() = + fun chips_twoPromotedNotifs_primaryAndSecondaryAreNotifsInOrder() = testScope.runTest { val latest by collectLastValue(underTest.chips) @@ -313,8 +321,16 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val secondIcon = mock<StatusBarIconView>() setNotifs( listOf( - activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon), - activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon), + activeNotificationModel( + key = "firstNotif", + statusBarChipIcon = firstIcon, + isPromoted = true, + ), + activeNotificationModel( + key = "secondNotif", + statusBarChipIcon = secondIcon, + isPromoted = true, + ), ) ) @@ -323,7 +339,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { } @Test - fun chips_threeNotifChips_topTwoShown() = + fun chips_threePromotedNotifs_topTwoShown() = testScope.runTest { val latest by collectLastValue(underTest.chips) @@ -332,9 +348,21 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val thirdIcon = mock<StatusBarIconView>() setNotifs( listOf( - activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon), - activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon), - activeNotificationModel(key = "thirdNotif", statusBarChipIcon = thirdIcon), + activeNotificationModel( + key = "firstNotif", + statusBarChipIcon = firstIcon, + isPromoted = true, + ), + activeNotificationModel( + key = "secondNotif", + statusBarChipIcon = secondIcon, + isPromoted = true, + ), + activeNotificationModel( + key = "thirdNotif", + statusBarChipIcon = thirdIcon, + isPromoted = true, + ), ) ) @@ -343,7 +371,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { } @Test - fun chips_callAndNotifs_primaryIsCallSecondaryIsNotif() = + fun chips_callAndPromotedNotifs_primaryIsCallSecondaryIsNotif() = testScope.runTest { val latest by collectLastValue(underTest.chips) @@ -351,10 +379,15 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val firstIcon = mock<StatusBarIconView>() setNotifs( listOf( - activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon), + activeNotificationModel( + key = "firstNotif", + statusBarChipIcon = firstIcon, + isPromoted = true, + ), activeNotificationModel( key = "secondNotif", statusBarChipIcon = mock<StatusBarIconView>(), + isPromoted = true, ), ) ) @@ -364,7 +397,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { } @Test - fun chips_screenRecordAndCallAndNotifs_notifsNotShown() = + fun chips_screenRecordAndCallAndPromotedNotifs_notifsNotShown() = testScope.runTest { val latest by collectLastValue(underTest.chips) @@ -375,6 +408,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { activeNotificationModel( key = "notif", statusBarChipIcon = mock<StatusBarIconView>(), + isPromoted = true, ) ) ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt index 20a19a9b1399..938da88a8819 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt @@ -26,11 +26,14 @@ import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent import com.android.systemui.statusbar.pipeline.shared.ui.composable.StatusBarRootFactory import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.statusbar.window.StatusBarWindowControllerStore +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import org.junit.Assert.assertThrows @@ -45,12 +48,14 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class StatusBarInitializerTest : SysuiTestCase() { + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val windowController = mock(StatusBarWindowController::class.java) private val windowControllerStore = mock(StatusBarWindowControllerStore::class.java) private val transaction = mock(FragmentTransaction::class.java) private val fragmentManager = mock(FragmentManager::class.java) private val fragmentHostManager = mock(FragmentHostManager::class.java) private val backgroundView = mock(ViewGroup::class.java) + private val statusBarModePerDisplayRepository = kosmos.fakeStatusBarModePerDisplayRepository @Before fun setup() { @@ -72,6 +77,7 @@ class StatusBarInitializerTest : SysuiTestCase() { statusBarRootFactory = mock(StatusBarRootFactory::class.java), componentFactory = mock(HomeStatusBarComponent.Factory::class.java), creationListeners = setOf(), + statusBarModePerDisplayRepository = statusBarModePerDisplayRepository, ) @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt new file mode 100644 index 000000000000..18eef33813f6 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt @@ -0,0 +1,76 @@ +/* + * 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.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.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) +class LightBarControllerStoreImplTest : SysuiTestCase() { + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val testScope = kosmos.testScope + private val fakeDisplayRepository = kosmos.displayRepository + + private val underTest = kosmos.lightBarControllerStoreImpl + + @Before + fun start() { + underTest.start() + } + + @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) } + + @Test + fun forDisplay_startsInstance() = + testScope.runTest { + val instance = underTest.forDisplay(DEFAULT_DISPLAY) + + verify(instance).start() + } + + @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/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt new file mode 100644 index 000000000000..a9920ec5e29b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt @@ -0,0 +1,70 @@ +/* + * 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.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) +class MultiDisplayStatusBarModeRepositoryStoreTest : SysuiTestCase() { + + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val testScope = kosmos.testScope + private val fakeDisplayRepository = kosmos.displayRepository + private val underTest by lazy { kosmos.multiDisplayStatusBarModeRepositoryStore } + + @Before + fun start() { + underTest.start() + } + + @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) } + + @Test + fun forDisplay_startsInstance() = + testScope.runTest { + val instance = underTest.forDisplay(DEFAULT_DISPLAY) + + verify(instance).start() + } + + @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/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt index 9f40f60db45f..99bda856818e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt @@ -18,11 +18,14 @@ package com.android.systemui.statusbar.notification.domain.interactor +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.coroutines.collectLastValue import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore @@ -44,7 +47,7 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() { private val testScope = kosmos.testScope private val activeNotificationListRepository = kosmos.activeNotificationListRepository - private val underTest = kosmos.activeNotificationsInteractor + private val underTest by lazy { kosmos.activeNotificationsInteractor } @Test fun testAllNotificationsCount() = @@ -65,14 +68,8 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() { val normalNotifs = listOf( - activeNotificationModel( - key = "notif1", - callType = CallType.None, - ), - activeNotificationModel( - key = "notif2", - callType = CallType.None, - ) + activeNotificationModel(key = "notif1", callType = CallType.None), + activeNotificationModel(key = "notif2", callType = CallType.None), ) activeNotificationListRepository.activeNotifications.value = @@ -129,10 +126,7 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() { val latest by collectLastValue(underTest.ongoingCallNotification) val ongoingNotif = - activeNotificationModel( - key = "ongoingNotif", - callType = CallType.Ongoing, - ) + activeNotificationModel(key = "ongoingNotif", callType = CallType.Ongoing) activeNotificationListRepository.activeNotifications.value = ActiveNotificationsStore.Builder() @@ -170,6 +164,62 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarNotifChips.FLAG_NAME) + fun promotedOngoingNotifications_flagOff_empty() = + testScope.runTest { + val latest by collectLastValue(underTest.promotedOngoingNotifications) + + val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true) + val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false) + + activeNotificationListRepository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { addIndividualNotif(promoted1) } + .apply { addIndividualNotif(notPromoted2) } + .build() + + assertThat(latest!!).isEmpty() + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun promotedOngoingNotifications_nonePromoted_empty() = + testScope.runTest { + val latest by collectLastValue(underTest.promotedOngoingNotifications) + + activeNotificationListRepository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { activeNotificationModel(key = "notif1", isPromoted = false) } + .apply { activeNotificationModel(key = "notif2", isPromoted = false) } + .apply { activeNotificationModel(key = "notif3", isPromoted = false) } + .build() + + assertThat(latest!!).isEmpty() + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun promotedOngoingNotifications_somePromoted_hasOnlyPromoted() = + testScope.runTest { + val latest by collectLastValue(underTest.promotedOngoingNotifications) + + val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true) + val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false) + val notPromoted3 = activeNotificationModel(key = "notif3", isPromoted = false) + val promoted4 = activeNotificationModel(key = "notif4", isPromoted = true) + + activeNotificationListRepository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { addIndividualNotif(promoted1) } + .apply { addIndividualNotif(notPromoted2) } + .apply { addIndividualNotif(notPromoted3) } + .apply { addIndividualNotif(promoted4) } + .build() + + assertThat(latest!!).containsExactly(promoted1, promoted4) + } + + @Test fun areAnyNotificationsPresent_isTrue() = testScope.runTest { val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt index 572a0c1b1869..183f9016a23b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt @@ -16,22 +16,25 @@ package com.android.systemui.statusbar.notification.domain.interactor import android.app.Notification -import android.os.Bundle +import android.app.Notification.FLAG_PROMOTED_ONGOING +import android.platform.test.annotations.EnableFlags import android.service.notification.StatusBarNotification import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.RankingBuilder import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi +import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider import com.android.systemui.statusbar.notification.shared.byKey +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -39,16 +42,16 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class RenderNotificationsListInteractorTest : SysuiTestCase() { - private val backgroundDispatcher = StandardTestDispatcher() - private val testScope = TestScope(backgroundDispatcher) + private val kosmos = testKosmos() + private val testScope = kosmos.testScope - private val notifsRepository = ActiveNotificationListRepository() - private val notifsInteractor = - ActiveNotificationsInteractor(notifsRepository, backgroundDispatcher) + private val notifsRepository = kosmos.activeNotificationListRepository + private val notifsInteractor = kosmos.activeNotificationsInteractor private val underTest = RenderNotificationListInteractor( notifsRepository, sectionStyleProvider = mock(), + promotedNotificationsProvider = kosmos.promotedNotificationsProvider, ) @Test @@ -85,12 +88,7 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() { assertThat(ranks) .containsExactlyEntriesIn( - mapOf( - "single" to 0, - "summary" to 1, - "child0" to 2, - "child1" to 3, - ) + mapOf("single" to 0, "summary" to 1, "child0" to 2, "child1" to 3) ) } @@ -126,6 +124,53 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() { assertThat(actual).containsAtLeastEntriesIn(expected) } + + @Test + @EnableFlags(PromotedNotificationUi.FLAG_NAME) + fun setRenderList_setsPromotionStatus() = + testScope.runTest { + val actual by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications) + + val notPromoted1 = mockNotificationEntry("key1", flag = null) + val promoted2 = mockNotificationEntry("key2", flag = FLAG_PROMOTED_ONGOING) + + underTest.setRenderedList(listOf(notPromoted1, promoted2)) + + assertThat(actual!!.size).isEqualTo(2) + + val first = actual!![0] + assertThat(first.key).isEqualTo("key1") + assertThat(first.isPromoted).isFalse() + + val second = actual!![1] + assertThat(second.key).isEqualTo("key2") + assertThat(second.isPromoted).isTrue() + } + + private fun mockNotificationEntry( + key: String, + rank: Int = 0, + flag: Int? = null, + ): NotificationEntry { + val nBuilder = Notification.Builder(context, "a") + if (flag != null) { + nBuilder.setFlag(flag, true) + } + val notification = nBuilder.build() + + val mockSbn = + mock<StatusBarNotification>() { + whenever(this.notification).thenReturn(notification) + whenever(packageName).thenReturn("com.android") + } + return mock<NotificationEntry> { + whenever(this.key).thenReturn(key) + whenever(this.icons).thenReturn(mock()) + whenever(this.representativeEntry).thenReturn(this) + whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build()) + whenever(this.sbn).thenReturn(mockSbn) + } + } } private fun mockGroupEntry( @@ -139,19 +184,3 @@ private fun mockGroupEntry( whenever(this.children).thenReturn(children) } } - -private fun mockNotificationEntry(key: String, rank: Int = 0): NotificationEntry { - val mockNotification = mock<Notification> { this.extras = Bundle() } - val mockSbn = - mock<StatusBarNotification>() { - whenever(notification).thenReturn(mockNotification) - whenever(packageName).thenReturn("com.android") - } - return mock<NotificationEntry> { - whenever(this.key).thenReturn(key) - whenever(this.icons).thenReturn(mock()) - whenever(this.representativeEntry).thenReturn(this) - whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build()) - whenever(this.sbn).thenReturn(mockSbn) - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt new file mode 100644 index 000000000000..a9dbe63e8f07 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.promoted + +import android.app.Notification +import android.app.Notification.FLAG_PROMOTED_ONGOING +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test + +@SmallTest +class PromotedNotificationsProviderTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private val underTest = kosmos.promotedNotificationsProvider + + @Test + @DisableFlags(PromotedNotificationUi.FLAG_NAME) + fun shouldPromote_uiFlagOff_false() { + val entry = createNotification(FLAG_PROMOTED_ONGOING) + + assertThat(underTest.shouldPromote(entry)).isFalse() + } + + @Test + @EnableFlags(PromotedNotificationUi.FLAG_NAME) + fun shouldPromote_uiFlagOn_notifDoesNotHaveFlag_false() { + val entry = createNotification(flag = null) + + assertThat(underTest.shouldPromote(entry)).isFalse() + } + + @Test + @EnableFlags(PromotedNotificationUi.FLAG_NAME) + fun shouldPromote_uiFlagOn_notifHasFlag_true() { + val entry = createNotification(FLAG_PROMOTED_ONGOING) + + assertThat(underTest.shouldPromote(entry)).isTrue() + } + + private fun createNotification(flag: Int? = null): NotificationEntry { + val n = Notification.Builder(context, "a") + if (flag != null) { + n.setFlag(flag, true) + } + + return NotificationEntryBuilder().setNotification(n.build()).build() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt index e9d88ccb0cbc..122cfdd1a045 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt @@ -16,16 +16,22 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import androidx.test.ext.junit.runners.AndroidJUnit4 +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.kosmos.testScope 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.scene.data.repository.Idle +import com.android.systemui.scene.data.repository.setSceneTransition import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs import com.android.systemui.testKosmos @@ -33,10 +39,12 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) -class NotificationLoggerViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class NotificationLoggerViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() @@ -46,9 +54,22 @@ class NotificationLoggerViewModelTest : SysuiTestCase() { private val powerInteractor = kosmos.powerInteractor private val windowRootViewVisibilityRepository = kosmos.windowRootViewVisibilityRepository - private val underTest = kosmos.notificationListLoggerViewModel + private val underTest by lazy { kosmos.notificationListLoggerViewModel } + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } @Test + @DisableSceneContainer fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsInteractive_true() = testScope.runTest { powerInteractor.setAwakeForTest() @@ -60,6 +81,7 @@ class NotificationLoggerViewModelTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun isLockscreenOrShadeInteractive_deviceIsAsleepAndShadeIsInteractive_false() = testScope.runTest { powerInteractor.setAsleepForTest() @@ -71,6 +93,7 @@ class NotificationLoggerViewModelTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsNotInteractive_false() = testScope.runTest { powerInteractor.setAwakeForTest() @@ -82,6 +105,54 @@ class NotificationLoggerViewModelTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun isLockscreenOrShadeInteractive_deviceAwakeAndShadeIsDisplayed_true() = + testScope.runTest { + powerInteractor.setAwakeForTest() + kosmos.setSceneTransition(Idle(Scenes.Shade)) + + val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive) + + assertThat(actual).isTrue() + } + + @Test + @EnableSceneContainer + fun isLockscreenOrShadeInteractive_deviceAwakeAndLockScreenIsDisplayed_true() = + testScope.runTest { + powerInteractor.setAwakeForTest() + kosmos.setSceneTransition(Idle(Scenes.Lockscreen)) + + val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive) + + assertThat(actual).isTrue() + } + + @Test + @EnableSceneContainer + fun isLockscreenOrShadeInteractive_deviceIsAsleepOnLockscreen_false() = + testScope.runTest { + powerInteractor.setAsleepForTest() + kosmos.setSceneTransition(Idle(Scenes.Lockscreen)) + + val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive) + + assertThat(actual).isFalse() + } + + @Test + @EnableSceneContainer + fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsClosed() = + testScope.runTest { + powerInteractor.setAwakeForTest() + kosmos.setSceneTransition(Idle(Scenes.Gone)) + + val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive) + + assertThat(actual).isFalse() + } + + @Test fun activeNotifications_hasNotifications() = testScope.runTest { activeNotificationListRepository.setActiveNotifs(5) @@ -135,6 +206,7 @@ class NotificationLoggerViewModelTest : SysuiTestCase() { assertThat(isOnLockScreen).isTrue() } + @Test fun isOnLockScreen_false() = testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java deleted file mode 100644 index 157f8189276a..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ /dev/null @@ -1,845 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone; - - -import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS; -import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO; - -import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND; -import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; -import static com.android.systemui.statusbar.StatusBarState.SHADE; - -import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.os.UserHandle; -import android.os.UserManager; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; -import android.provider.Settings; -import android.testing.TestableLooper; -import android.view.LayoutInflater; -import android.view.View; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.keyguard.CarrierTextController; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.KeyguardUpdateMonitorCallback; -import com.android.keyguard.logging.KeyguardLogger; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.battery.BatteryMeterViewController; -import com.android.systemui.flags.DisableSceneContainer; -import com.android.systemui.flags.EnableSceneContainer; -import com.android.systemui.kosmos.KosmosJavaAdapter; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.res.R; -import com.android.systemui.shade.ShadeViewStateProvider; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.SysuiStatusBarStateController; -import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore; -import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; -import com.android.systemui.statusbar.phone.ui.StatusBarIconController; -import com.android.systemui.statusbar.phone.ui.TintedIconManager; -import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.UserInfoController; -import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.settings.SecureSettings; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidJUnit4.class) -@TestableLooper.RunWithLooper -public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { - @Mock - private CarrierTextController mCarrierTextController; - @Mock - private ConfigurationController mConfigurationController; - @Mock - private SystemStatusAnimationScheduler mAnimationScheduler; - @Mock - private BatteryController mBatteryController; - @Mock - private UserInfoController mUserInfoController; - @Mock - private StatusBarIconController mStatusBarIconController; - @Mock - private TintedIconManager.Factory mIconManagerFactory; - @Mock - private TintedIconManager mIconManager; - @Mock - private BatteryMeterViewController mBatteryMeterViewController; - @Mock - private KeyguardStateController mKeyguardStateController; - @Mock - private KeyguardBypassController mKeyguardBypassController; - @Mock - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - private BiometricUnlockController mBiometricUnlockController; - @Mock - private SysuiStatusBarStateController mStatusBarStateController; - @Mock - private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider; - @Mock - private StatusBarContentInsetsProviderStore mStatusBarContentInsetsProviderStore; - @Mock - private UserManager mUserManager; - @Mock - private StatusBarUserChipViewModel mStatusBarUserChipViewModel; - @Captor - private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor; - @Captor - private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor; - @Mock private SecureSettings mSecureSettings; - @Mock private CommandQueue mCommandQueue; - @Mock private KeyguardLogger mLogger; - @Mock private StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory; - - private TestShadeViewStateProvider mShadeViewStateProvider; - private KeyguardStatusBarView mKeyguardStatusBarView; - private KeyguardStatusBarViewController mController; - private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); - private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock()); - private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); - - @Before - public void setup() throws Exception { - mShadeViewStateProvider = new TestShadeViewStateProvider(); - - MockitoAnnotations.initMocks(this); - when(mStatusBarContentInsetsProviderStore.getDefaultDisplay()) - .thenReturn(mStatusBarContentInsetsProvider); - when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager); - - allowTestableLooperAsMainThread(); - TestableLooper.get(this).runWithLooper(() -> { - mKeyguardStatusBarView = - spy((KeyguardStatusBarView) LayoutInflater.from(mContext) - .inflate(R.layout.keyguard_status_bar, null)); - when(mKeyguardStatusBarView.getDisplay()).thenReturn(mContext.getDisplay()); - }); - - mController = createController(); - } - - private KeyguardStatusBarViewController createController() { - return new KeyguardStatusBarViewController( - mKeyguardStatusBarView, - mCarrierTextController, - mConfigurationController, - mAnimationScheduler, - mBatteryController, - mUserInfoController, - mStatusBarIconController, - mIconManagerFactory, - mBatteryMeterViewController, - mShadeViewStateProvider, - mKeyguardStateController, - mKeyguardBypassController, - mKeyguardUpdateMonitor, - mKosmos.getKeyguardStatusBarViewModel(), - mBiometricUnlockController, - mStatusBarStateController, - mStatusBarContentInsetsProviderStore, - mUserManager, - mStatusBarUserChipViewModel, - mSecureSettings, - mCommandQueue, - mFakeExecutor, - mBackgroundExecutor, - mLogger, - mStatusOverlayHoverListenerFactory, - mKosmos.getCommunalSceneInteractor() - ); - } - - @Test - @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND) - public void onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() { - mController.onViewAttached(); - - runAllScheduled(); - verify(mConfigurationController).addCallback(any()); - verify(mAnimationScheduler).addCallback(any()); - verify(mUserInfoController).addCallback(any()); - verify(mCommandQueue).addCallback(any()); - verify(mStatusBarIconController).addIconGroup(any()); - verify(mUserManager).isUserSwitcherEnabled(anyBoolean()); - } - - @Test - @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND) - public void onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() { - mController.onViewAttached(); - - verify(mConfigurationController).addCallback(any()); - verify(mAnimationScheduler).addCallback(any()); - verify(mUserInfoController).addCallback(any()); - verify(mCommandQueue).addCallback(any()); - verify(mStatusBarIconController).addIconGroup(any()); - verify(mUserManager).isUserSwitcherEnabled(anyBoolean()); - } - - @Test - @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND) - public void - onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() { - mController.onViewAttached(); - runAllScheduled(); - verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture()); - clearInvocations(mUserManager); - clearInvocations(mKeyguardStatusBarView); - - mConfigurationListenerCaptor.getValue().onConfigChanged(null); - - runAllScheduled(); - verify(mUserManager).isUserSwitcherEnabled(anyBoolean()); - verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean()); - } - - @Test - @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND) - public void - onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() { - mController.onViewAttached(); - verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture()); - clearInvocations(mUserManager); - clearInvocations(mKeyguardStatusBarView); - - mConfigurationListenerCaptor.getValue().onConfigChanged(null); - verify(mUserManager).isUserSwitcherEnabled(anyBoolean()); - verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean()); - } - - @Test - @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND) - public void - onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() { - mController.onViewAttached(); - runAllScheduled(); - verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture()); - clearInvocations(mUserManager); - clearInvocations(mKeyguardStatusBarView); - - mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true); - - runAllScheduled(); - verify(mUserManager).isUserSwitcherEnabled(anyBoolean()); - verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean()); - } - - @Test - @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND) - public void - onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() { - mController.onViewAttached(); - verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture()); - clearInvocations(mUserManager); - clearInvocations(mKeyguardStatusBarView); - - mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true); - verify(mUserManager).isUserSwitcherEnabled(anyBoolean()); - verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean()); - } - - @Test - public void onViewDetached_callbacksUnregistered() { - // Set everything up first. - mController.onViewAttached(); - - mController.onViewDetached(); - - verify(mConfigurationController).removeCallback(any()); - verify(mAnimationScheduler).removeCallback(any()); - verify(mUserInfoController).removeCallback(any()); - verify(mCommandQueue).removeCallback(any()); - verify(mStatusBarIconController).removeIconGroup(any()); - } - - @Test - @DisableSceneContainer - public void onViewReAttached_flagOff_iconManagerNotReRegistered() { - mController.onViewAttached(); - mController.onViewDetached(); - reset(mStatusBarIconController); - - mController.onViewAttached(); - - verify(mStatusBarIconController, never()).addIconGroup(any()); - } - - @Test - @EnableSceneContainer - public void onViewReAttached_flagOn_iconManagerReRegistered() { - mController.onViewAttached(); - mController.onViewDetached(); - reset(mStatusBarIconController); - - mController.onViewAttached(); - - verify(mStatusBarIconController).addIconGroup(any()); - } - - @Test - @DisableSceneContainer - public void setBatteryListening_true_callbackAdded() { - mController.setBatteryListening(true); - - verify(mBatteryController).addCallback(any()); - } - - @Test - @DisableSceneContainer - public void setBatteryListening_false_callbackRemoved() { - // First set to true so that we know setting to false is a change in state. - mController.setBatteryListening(true); - - mController.setBatteryListening(false); - - verify(mBatteryController).removeCallback(any()); - } - - @Test - @DisableSceneContainer - public void setBatteryListening_trueThenTrue_callbackAddedOnce() { - mController.setBatteryListening(true); - mController.setBatteryListening(true); - - verify(mBatteryController).addCallback(any()); - } - - @Test - @EnableSceneContainer - public void setBatteryListening_true_flagOn_callbackNotAdded() { - mController.setBatteryListening(true); - - verify(mBatteryController, never()).addCallback(any()); - } - - @Test - public void updateTopClipping_viewClippingUpdated() { - int viewTop = 20; - mKeyguardStatusBarView.setTop(viewTop); - int notificationPanelTop = 30; - - mController.updateTopClipping(notificationPanelTop); - - assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo( - notificationPanelTop - viewTop); - } - - @Test - public void setNotTopClipping_viewClippingUpdatedToZero() { - // Start out with some amount of top clipping. - mController.updateTopClipping(50); - assertThat(mKeyguardStatusBarView.getClipBounds().top).isGreaterThan(0); - - mController.setNoTopClipping(); - - assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo(0); - } - - @Test - @DisableSceneContainer - public void updateViewState_alphaAndVisibilityGiven_viewUpdated() { - // Verify the initial values so we know the method triggers changes. - assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(1f); - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE); - - float newAlpha = 0.5f; - int newVisibility = View.INVISIBLE; - mController.updateViewState(newAlpha, newVisibility); - - assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(newAlpha); - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(newVisibility); - } - - @Test - @DisableSceneContainer - public void updateViewState_paramVisibleButIsDisabled_viewIsInvisible() { - mController.onViewAttached(); - setDisableSystemIcons(true); - - mController.updateViewState(1f, View.VISIBLE); - - // Since we're disabled, we stay invisible - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); - } - - @Test - @DisableSceneContainer - public void updateViewState_notKeyguardState_nothingUpdated() { - mController.onViewAttached(); - updateStateToNotKeyguard(); - - float oldAlpha = mKeyguardStatusBarView.getAlpha(); - - mController.updateViewState(); - - assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(oldAlpha); - } - - @Test - @DisableSceneContainer - public void updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() { - mController.onViewAttached(); - updateStateToKeyguard(); - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE); - - when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(true); - when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true); - onFinishedGoingToSleep(); - - mController.updateViewState(); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); - } - - @Test - @DisableSceneContainer - public void updateViewState_bypassNotEnabled_viewShown() { - mController.onViewAttached(); - updateStateToKeyguard(); - - when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(true); - when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false); - onFinishedGoingToSleep(); - - mController.updateViewState(); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE); - } - - @Test - @DisableSceneContainer - public void updateViewState_shouldNotListenForFace_viewShown() { - mController.onViewAttached(); - updateStateToKeyguard(); - - when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(false); - when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true); - onFinishedGoingToSleep(); - - mController.updateViewState(); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE); - } - - @Test - @DisableSceneContainer - public void updateViewState_panelExpandedHeightZero_viewHidden() { - mController.onViewAttached(); - updateStateToKeyguard(); - - mShadeViewStateProvider.setPanelViewExpandedHeight(0); - - mController.updateViewState(); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); - } - - @Test - @DisableSceneContainer - public void updateViewState_dragProgressOne_viewHidden() { - mController.onViewAttached(); - updateStateToKeyguard(); - - mShadeViewStateProvider.setLockscreenShadeDragProgress(1f); - - mController.updateViewState(); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); - } - - @Test - @DisableSceneContainer - public void updateViewState_disableSystemInfoFalse_viewShown() { - mController.onViewAttached(); - updateStateToKeyguard(); - setDisableSystemInfo(false); - - mController.updateViewState(); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE); - } - - @Test - @DisableSceneContainer - public void updateViewState_disableSystemInfoTrue_viewHidden() { - mController.onViewAttached(); - updateStateToKeyguard(); - setDisableSystemInfo(true); - - mController.updateViewState(); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); - } - - @Test - @DisableSceneContainer - public void updateViewState_disableSystemIconsFalse_viewShown() { - mController.onViewAttached(); - updateStateToKeyguard(); - setDisableSystemIcons(false); - - mController.updateViewState(); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE); - } - - @Test - @DisableSceneContainer - public void updateViewState_disableSystemIconsTrue_viewHidden() { - mController.onViewAttached(); - updateStateToKeyguard(); - setDisableSystemIcons(true); - - mController.updateViewState(); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); - } - - @Test - @DisableSceneContainer - public void updateViewState_dozingTrue_flagOff_viewHidden() { - mController.init(); - mController.onViewAttached(); - updateStateToKeyguard(); - - mController.setDozing(true); - mController.updateViewState(); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); - } - - @Test - @DisableSceneContainer - public void updateViewState_dozingFalse_flagOff_viewShown() { - mController.init(); - mController.onViewAttached(); - updateStateToKeyguard(); - - mController.setDozing(false); - mController.updateViewState(); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE); - } - - @Test - @EnableSceneContainer - public void updateViewState_flagOn_doesNothing() { - mController.init(); - mController.onViewAttached(); - updateStateToKeyguard(); - - mKeyguardStatusBarView.setVisibility(View.GONE); - mKeyguardStatusBarView.setAlpha(0.456f); - - mController.updateViewState(); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.GONE); - assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f); - } - - @Test - @EnableSceneContainer - public void updateViewStateWithAlphaAndVis_flagOn_doesNothing() { - mController.init(); - mController.onViewAttached(); - updateStateToKeyguard(); - - mKeyguardStatusBarView.setVisibility(View.GONE); - mKeyguardStatusBarView.setAlpha(0.456f); - - mController.updateViewState(0.789f, View.VISIBLE); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.GONE); - assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f); - } - - @Test - @EnableSceneContainer - public void setAlpha_flagOn_doesNothing() { - mController.init(); - mController.onViewAttached(); - updateStateToKeyguard(); - - mKeyguardStatusBarView.setAlpha(0.456f); - - mController.setAlpha(0.123f); - - assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f); - } - - @Test - @EnableSceneContainer - public void setDozing_flagOn_doesNothing() { - mController.init(); - mController.onViewAttached(); - updateStateToKeyguard(); - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE); - - mController.setDozing(true); - mController.updateViewState(); - - // setDozing(true) should typically cause the view to hide. But since the flag is on, we - // should ignore these set dozing calls and stay the same visibility. - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE); - } - - @Test - @DisableSceneContainer - public void setAlpha_explicitAlpha_setsExplicitAlpha() { - mController.onViewAttached(); - updateStateToKeyguard(); - - mController.setAlpha(0.5f); - - assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.5f); - } - - @Test - @DisableSceneContainer - public void setAlpha_explicitAlpha_thenMinusOneAlpha_setsAlphaBasedOnDefaultCriteria() { - mController.onViewAttached(); - updateStateToKeyguard(); - - mController.setAlpha(0.5f); - mController.setAlpha(-1f); - - assertThat(mKeyguardStatusBarView.getAlpha()).isGreaterThan(0); - assertThat(mKeyguardStatusBarView.getAlpha()).isNotEqualTo(0.5f); - } - - // TODO(b/195442899): Add more tests for #updateViewState once CLs are finalized. - - @Test - @DisableSceneContainer - public void updateForHeadsUp_headsUpShouldBeVisible_viewHidden() { - mController.onViewAttached(); - updateStateToKeyguard(); - mKeyguardStatusBarView.setVisibility(View.VISIBLE); - - mShadeViewStateProvider.setShouldHeadsUpBeVisible(true); - mController.updateForHeadsUp(/* animate= */ false); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); - } - - @Test - @DisableSceneContainer - public void updateForHeadsUp_headsUpShouldNotBeVisible_viewShown() { - mController.onViewAttached(); - updateStateToKeyguard(); - - // Start with the opposite state. - mShadeViewStateProvider.setShouldHeadsUpBeVisible(true); - mController.updateForHeadsUp(/* animate= */ false); - - mShadeViewStateProvider.setShouldHeadsUpBeVisible(false); - mController.updateForHeadsUp(/* animate= */ false); - - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE); - } - - @Test - public void testNewUserSwitcherDisablesAvatar_newUiOn() { - // GIVEN the status bar user switcher chip is enabled - when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(true); - - // WHEN the controller is created - mController = createController(); - - // THEN keyguard status bar view avatar is disabled - assertThat(mKeyguardStatusBarView.isKeyguardUserAvatarEnabled()).isFalse(); - } - - @Test - public void testNewUserSwitcherDisablesAvatar_newUiOff() { - // GIVEN the status bar user switcher chip is disabled - when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(false); - - // WHEN the controller is created - mController = createController(); - - // THEN keyguard status bar view avatar is enabled - assertThat(mKeyguardStatusBarView.isKeyguardUserAvatarEnabled()).isTrue(); - } - - @Test - public void testBlockedIcons_obeysSettingForVibrateIcon_settingOff() { - String str = mContext.getString(com.android.internal.R.string.status_bar_volume); - - // GIVEN the setting is off - when(mSecureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0)) - .thenReturn(0); - - // WHEN CollapsedStatusBarFragment builds the blocklist - mController.updateBlockedIcons(); - - // THEN status_bar_volume SHOULD be present in the list - boolean contains = mController.getBlockedIcons().contains(str); - assertTrue(contains); - } - - @Test - public void testBlockedIcons_obeysSettingForVibrateIcon_settingOn() { - String str = mContext.getString(com.android.internal.R.string.status_bar_volume); - - // GIVEN the setting is ON - when(mSecureSettings.getIntForUser(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0, - UserHandle.USER_CURRENT)) - .thenReturn(1); - - // WHEN CollapsedStatusBarFragment builds the blocklist - mController.updateBlockedIcons(); - - // THEN status_bar_volume SHOULD NOT be present in the list - boolean contains = mController.getBlockedIcons().contains(str); - assertFalse(contains); - } - - private void updateStateToNotKeyguard() { - updateStatusBarState(SHADE); - } - - private void updateStateToKeyguard() { - updateStatusBarState(KEYGUARD); - } - - private void updateStatusBarState(int state) { - ArgumentCaptor<StatusBarStateController.StateListener> statusBarStateListenerCaptor = - ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); - verify(mStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture()); - StatusBarStateController.StateListener callback = statusBarStateListenerCaptor.getValue(); - - callback.onStateChanged(state); - } - - @Test - @DisableSceneContainer - public void animateKeyguardStatusBarIn_isDisabled_viewStillHidden() { - mController.onViewAttached(); - updateStateToKeyguard(); - setDisableSystemInfo(true); - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); - - mController.animateKeyguardStatusBarIn(); - - // Since we're disabled, we don't actually animate in and stay invisible - assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE); - } - - /** - * Calls {@link com.android.keyguard.KeyguardUpdateMonitorCallback#onFinishedGoingToSleep(int)} - * to ensure values are updated properly. - */ - private void onFinishedGoingToSleep() { - ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateCallbackCaptor = - ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); - verify(mKeyguardUpdateMonitor).registerCallback(keyguardUpdateCallbackCaptor.capture()); - KeyguardUpdateMonitorCallback callback = keyguardUpdateCallbackCaptor.getValue(); - - callback.onFinishedGoingToSleep(0); - } - - private void setDisableSystemInfo(boolean disabled) { - CommandQueue.Callbacks callback = getCommandQueueCallback(); - int disabled1 = disabled ? DISABLE_SYSTEM_INFO : 0; - callback.disable(mContext.getDisplayId(), disabled1, 0, false); - } - - private void setDisableSystemIcons(boolean disabled) { - CommandQueue.Callbacks callback = getCommandQueueCallback(); - int disabled2 = disabled ? DISABLE2_SYSTEM_ICONS : 0; - callback.disable(mContext.getDisplayId(), 0, disabled2, false); - } - - private CommandQueue.Callbacks getCommandQueueCallback() { - ArgumentCaptor<CommandQueue.Callbacks> captor = - ArgumentCaptor.forClass(CommandQueue.Callbacks.class); - verify(mCommandQueue).addCallback(captor.capture()); - return captor.getValue(); - } - - private void runAllScheduled() { - mBackgroundExecutor.runAllReady(); - mFakeExecutor.runAllReady(); - } - - private static class TestShadeViewStateProvider - implements ShadeViewStateProvider { - - TestShadeViewStateProvider() {} - - private float mPanelViewExpandedHeight = 100f; - private boolean mShouldHeadsUpBeVisible = false; - private float mLockscreenShadeDragProgress = 0f; - - @Override - public float getPanelViewExpandedHeight() { - return mPanelViewExpandedHeight; - } - - @Override - public boolean shouldHeadsUpBeVisible() { - return mShouldHeadsUpBeVisible; - } - - @Override - public float getLockscreenShadeDragProgress() { - return mLockscreenShadeDragProgress; - } - - public void setPanelViewExpandedHeight(float panelViewExpandedHeight) { - this.mPanelViewExpandedHeight = panelViewExpandedHeight; - } - - public void setShouldHeadsUpBeVisible(boolean shouldHeadsUpBeVisible) { - this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible; - } - - public void setLockscreenShadeDragProgress(float lockscreenShadeDragProgress) { - this.mLockscreenShadeDragProgress = lockscreenShadeDragProgress; - } - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt new file mode 100644 index 000000000000..b815c6ce0c51 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt @@ -0,0 +1,867 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.phone + +import android.app.StatusBarManager +import android.graphics.Insets +import android.os.UserHandle +import android.os.UserManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.provider.Settings +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import android.testing.ViewUtils +import android.view.LayoutInflater +import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.CarrierTextController +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.keyguard.logging.KeyguardLogger +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.battery.BatteryMeterViewController +import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository +import com.android.systemui.communal.domain.interactor.communalSceneInteractor +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.res.R +import com.android.systemui.shade.ShadeViewStateProvider +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler +import com.android.systemui.statusbar.phone.ui.StatusBarIconController +import com.android.systemui.statusbar.phone.ui.TintedIconManager +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.policy.UserInfoController +import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel +import com.android.systemui.statusbar.ui.viewmodel.statusBarUserChipViewModel +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +@RunWithLooper(setAsMainLooper = true) +class KeyguardStatusBarViewControllerTest : SysuiTestCase() { + private lateinit var kosmos: Kosmos + private lateinit var testScope: TestScope + + @Mock private lateinit var carrierTextController: CarrierTextController + + @Mock private lateinit var configurationController: ConfigurationController + + @Mock private lateinit var animationScheduler: SystemStatusAnimationScheduler + + @Mock private lateinit var batteryController: BatteryController + + @Mock private lateinit var userInfoController: UserInfoController + + @Mock private lateinit var statusBarIconController: StatusBarIconController + + @Mock private lateinit var iconManagerFactory: TintedIconManager.Factory + + @Mock private lateinit var iconManager: TintedIconManager + + @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController + + @Mock private lateinit var keyguardStateController: KeyguardStateController + + @Mock private lateinit var keyguardBypassController: KeyguardBypassController + + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + + @Mock private lateinit var biometricUnlockController: BiometricUnlockController + + @Mock + private lateinit var statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore + + @Mock private lateinit var userManager: UserManager + + @Captor + private lateinit var configurationListenerCaptor: + ArgumentCaptor<ConfigurationController.ConfigurationListener> + + @Captor + private lateinit var keyguardCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback> + + @Mock private lateinit var secureSettings: SecureSettings + + @Mock private lateinit var commandQueue: CommandQueue + + @Mock private lateinit var logger: KeyguardLogger + + @Mock private lateinit var statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory + + private lateinit var shadeViewStateProvider: TestShadeViewStateProvider + + private lateinit var keyguardStatusBarView: KeyguardStatusBarView + private lateinit var controller: KeyguardStatusBarViewController + private val fakeExecutor = FakeExecutor(FakeSystemClock()) + private val backgroundExecutor = FakeExecutor(FakeSystemClock()) + + private lateinit var looper: TestableLooper + + @Before + @Throws(Exception::class) + fun setup() { + looper = TestableLooper.get(this) + kosmos = testKosmos() + testScope = kosmos.testScope + shadeViewStateProvider = TestShadeViewStateProvider() + + Mockito.`when`( + kosmos.statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation() + ) + .thenReturn(Insets.of(0, 0, 0, 0)) + + MockitoAnnotations.initMocks(this) + + Mockito.`when`(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any())) + .thenReturn(iconManager) + Mockito.`when`(statusBarContentInsetsProviderStore.defaultDisplay) + .thenReturn(kosmos.statusBarContentInsetsProvider) + allowTestableLooperAsMainThread() + looper.runWithLooper { + keyguardStatusBarView = + Mockito.spy( + LayoutInflater.from(mContext).inflate(R.layout.keyguard_status_bar, null) + as KeyguardStatusBarView + ) + Mockito.`when`(keyguardStatusBarView.getDisplay()).thenReturn(mContext.display) + } + + controller = createController() + } + + private fun createController(): KeyguardStatusBarViewController { + return KeyguardStatusBarViewController( + kosmos.testDispatcher, + keyguardStatusBarView, + carrierTextController, + configurationController, + animationScheduler, + batteryController, + userInfoController, + statusBarIconController, + iconManagerFactory, + batteryMeterViewController, + shadeViewStateProvider, + keyguardStateController, + keyguardBypassController, + keyguardUpdateMonitor, + kosmos.keyguardStatusBarViewModel, + biometricUnlockController, + kosmos.statusBarStateController, + statusBarContentInsetsProviderStore, + userManager, + kosmos.statusBarUserChipViewModel, + secureSettings, + commandQueue, + fakeExecutor, + backgroundExecutor, + logger, + statusOverlayHoverListenerFactory, + kosmos.communalSceneInteractor, + kosmos.glanceableHubToLockscreenTransitionViewModel, + kosmos.lockscreenToGlanceableHubTransitionViewModel, + ) + } + + @Test + @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND) + fun onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() { + controller.onViewAttached() + + runAllScheduled() + Mockito.verify(configurationController).addCallback(ArgumentMatchers.any()) + Mockito.verify(animationScheduler).addCallback(ArgumentMatchers.any()) + Mockito.verify(userInfoController).addCallback(ArgumentMatchers.any()) + Mockito.verify(commandQueue).addCallback(ArgumentMatchers.any()) + Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any()) + Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean()) + } + + @Test + @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND) + fun onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() { + controller.onViewAttached() + + Mockito.verify(configurationController).addCallback(ArgumentMatchers.any()) + Mockito.verify(animationScheduler).addCallback(ArgumentMatchers.any()) + Mockito.verify(userInfoController).addCallback(ArgumentMatchers.any()) + Mockito.verify(commandQueue).addCallback(ArgumentMatchers.any()) + Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any()) + Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean()) + } + + @Test + @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND) + fun onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() { + controller.onViewAttached() + runAllScheduled() + Mockito.verify(configurationController).addCallback(configurationListenerCaptor.capture()) + Mockito.clearInvocations(userManager) + Mockito.clearInvocations(keyguardStatusBarView) + + configurationListenerCaptor.value.onConfigChanged(null) + + runAllScheduled() + Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean()) + Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean()) + } + + @Test + @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND) + fun onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() { + controller.onViewAttached() + Mockito.verify(configurationController).addCallback(configurationListenerCaptor.capture()) + Mockito.clearInvocations(userManager) + Mockito.clearInvocations(keyguardStatusBarView) + + configurationListenerCaptor.value.onConfigChanged(null) + Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean()) + Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean()) + } + + @Test + @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND) + fun onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() { + controller.onViewAttached() + runAllScheduled() + Mockito.verify(keyguardUpdateMonitor).registerCallback(keyguardCallbackCaptor.capture()) + Mockito.clearInvocations(userManager) + Mockito.clearInvocations(keyguardStatusBarView) + + keyguardCallbackCaptor.value.onKeyguardVisibilityChanged(true) + + runAllScheduled() + Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean()) + Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean()) + } + + @Test + @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND) + fun onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() { + controller.onViewAttached() + Mockito.verify(keyguardUpdateMonitor).registerCallback(keyguardCallbackCaptor.capture()) + Mockito.clearInvocations(userManager) + Mockito.clearInvocations(keyguardStatusBarView) + + keyguardCallbackCaptor.value.onKeyguardVisibilityChanged(true) + Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean()) + Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean()) + } + + @Test + fun onViewDetached_callbacksUnregistered() { + // Set everything up first. + controller.onViewAttached() + + controller.onViewDetached() + + Mockito.verify(configurationController).removeCallback(ArgumentMatchers.any()) + Mockito.verify(animationScheduler).removeCallback(ArgumentMatchers.any()) + Mockito.verify(userInfoController).removeCallback(ArgumentMatchers.any()) + Mockito.verify(commandQueue).removeCallback(ArgumentMatchers.any()) + Mockito.verify(statusBarIconController).removeIconGroup(ArgumentMatchers.any()) + } + + @Test + @DisableSceneContainer + fun onViewReAttached_flagOff_iconManagerNotReRegistered() { + controller.onViewAttached() + controller.onViewDetached() + Mockito.reset(statusBarIconController) + + controller.onViewAttached() + + Mockito.verify(statusBarIconController, Mockito.never()) + .addIconGroup(ArgumentMatchers.any()) + } + + @Test + @EnableSceneContainer + fun onViewReAttached_flagOn_iconManagerReRegistered() { + controller.onViewAttached() + controller.onViewDetached() + Mockito.reset(statusBarIconController) + + controller.onViewAttached() + + Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any()) + } + + @Test + @DisableSceneContainer + fun setBatteryListening_true_callbackAdded() { + controller.setBatteryListening(true) + + Mockito.verify(batteryController).addCallback(ArgumentMatchers.any()) + } + + @Test + @DisableSceneContainer + fun setBatteryListening_false_callbackRemoved() { + // First set to true so that we know setting to false is a change in state. + controller.setBatteryListening(true) + + controller.setBatteryListening(false) + + Mockito.verify(batteryController).removeCallback(ArgumentMatchers.any()) + } + + @Test + @DisableSceneContainer + fun setBatteryListening_trueThenTrue_callbackAddedOnce() { + controller.setBatteryListening(true) + controller.setBatteryListening(true) + + Mockito.verify(batteryController).addCallback(ArgumentMatchers.any()) + } + + @Test + @EnableSceneContainer + fun setBatteryListening_true_flagOn_callbackNotAdded() { + controller.setBatteryListening(true) + + Mockito.verify(batteryController, Mockito.never()).addCallback(ArgumentMatchers.any()) + } + + @Test + fun updateTopClipping_viewClippingUpdated() { + val viewTop = 20 + keyguardStatusBarView.top = viewTop + val notificationPanelTop = 30 + + controller.updateTopClipping(notificationPanelTop) + + Truth.assertThat(keyguardStatusBarView.clipBounds.top) + .isEqualTo(notificationPanelTop - viewTop) + } + + @Test + fun setNotTopClipping_viewClippingUpdatedToZero() { + // Start out with some amount of top clipping. + controller.updateTopClipping(50) + Truth.assertThat(keyguardStatusBarView.clipBounds.top).isGreaterThan(0) + + controller.setNoTopClipping() + + Truth.assertThat(keyguardStatusBarView.clipBounds.top).isEqualTo(0) + } + + @Test + @DisableSceneContainer + fun updateViewState_alphaAndVisibilityGiven_viewUpdated() { + // Verify the initial values so we know the method triggers changes. + Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(1f) + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + + val newAlpha = 0.5f + val newVisibility = View.INVISIBLE + controller.updateViewState(newAlpha, newVisibility) + + Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(newAlpha) + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(newVisibility) + } + + @Test + @DisableSceneContainer + fun updateViewState_paramVisibleButIsDisabled_viewIsInvisible() { + controller.onViewAttached() + setDisableSystemIcons(true) + + controller.updateViewState(1f, View.VISIBLE) + + // Since we're disabled, we stay invisible + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + } + + @Test + @DisableSceneContainer + fun updateViewState_notKeyguardState_nothingUpdated() { + controller.onViewAttached() + updateStateToNotKeyguard() + + val oldAlpha = keyguardStatusBarView.alpha + + controller.updateViewState() + + Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(oldAlpha) + } + + @Test + @DisableSceneContainer + fun updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() { + controller.onViewAttached() + updateStateToKeyguard() + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + + Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true) + Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true) + onFinishedGoingToSleep() + + controller.updateViewState() + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + } + + @Test + @DisableSceneContainer + fun updateViewState_bypassNotEnabled_viewShown() { + controller.onViewAttached() + updateStateToKeyguard() + + Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true) + Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(false) + onFinishedGoingToSleep() + + controller.updateViewState() + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @DisableSceneContainer + fun updateViewState_shouldNotListenForFace_viewShown() { + controller.onViewAttached() + updateStateToKeyguard() + + Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false) + Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true) + onFinishedGoingToSleep() + + controller.updateViewState() + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @DisableSceneContainer + fun updateViewState_panelExpandedHeightZero_viewHidden() { + controller.onViewAttached() + updateStateToKeyguard() + + shadeViewStateProvider.panelViewExpandedHeight = 0f + + controller.updateViewState() + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + } + + @Test + @DisableSceneContainer + fun updateViewState_dragProgressOne_viewHidden() { + controller.onViewAttached() + updateStateToKeyguard() + + shadeViewStateProvider.lockscreenShadeDragProgress = 1f + + controller.updateViewState() + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + } + + @Test + @DisableSceneContainer + fun updateViewState_disableSystemInfoFalse_viewShown() { + controller.onViewAttached() + updateStateToKeyguard() + setDisableSystemInfo(false) + + controller.updateViewState() + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @DisableSceneContainer + fun updateViewState_disableSystemInfoTrue_viewHidden() { + controller.onViewAttached() + updateStateToKeyguard() + setDisableSystemInfo(true) + + controller.updateViewState() + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + } + + @Test + @DisableSceneContainer + fun updateViewState_disableSystemIconsFalse_viewShown() { + controller.onViewAttached() + updateStateToKeyguard() + setDisableSystemIcons(false) + + controller.updateViewState() + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @DisableSceneContainer + fun updateViewState_disableSystemIconsTrue_viewHidden() { + controller.onViewAttached() + updateStateToKeyguard() + setDisableSystemIcons(true) + + controller.updateViewState() + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + } + + @Test + @DisableSceneContainer + fun updateViewState_dozingTrue_flagOff_viewHidden() { + controller.init() + controller.onViewAttached() + updateStateToKeyguard() + + controller.setDozing(true) + controller.updateViewState() + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + } + + @Test + @DisableSceneContainer + fun updateViewState_dozingFalse_flagOff_viewShown() { + controller.init() + controller.onViewAttached() + updateStateToKeyguard() + + controller.setDozing(false) + controller.updateViewState() + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @EnableSceneContainer + fun updateViewState_flagOn_doesNothing() { + controller.init() + controller.onViewAttached() + updateStateToKeyguard() + + keyguardStatusBarView.visibility = View.GONE + keyguardStatusBarView.alpha = 0.456f + + controller.updateViewState() + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE) + Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f) + } + + @Test + @EnableSceneContainer + fun updateViewStateWithAlphaAndVis_flagOn_doesNothing() { + controller.init() + controller.onViewAttached() + updateStateToKeyguard() + + keyguardStatusBarView.visibility = View.GONE + keyguardStatusBarView.alpha = 0.456f + + controller.updateViewState(0.789f, View.VISIBLE) + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE) + Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f) + } + + @Test + @EnableSceneContainer + fun setAlpha_flagOn_doesNothing() { + controller.init() + controller.onViewAttached() + updateStateToKeyguard() + + keyguardStatusBarView.alpha = 0.456f + + controller.setAlpha(0.123f) + + Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f) + } + + @Test + @EnableSceneContainer + fun setDozing_flagOn_doesNothing() { + controller.init() + controller.onViewAttached() + updateStateToKeyguard() + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + + controller.setDozing(true) + controller.updateViewState() + + // setDozing(true) should typically cause the view to hide. But since the flag is on, we + // should ignore these set dozing calls and stay the same visibility. + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @DisableSceneContainer + fun setAlpha_explicitAlpha_setsExplicitAlpha() { + controller.onViewAttached() + updateStateToKeyguard() + + controller.setAlpha(0.5f) + + Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.5f) + } + + @Test + @DisableSceneContainer + fun setAlpha_explicitAlpha_thenMinusOneAlpha_setsAlphaBasedOnDefaultCriteria() { + controller.onViewAttached() + updateStateToKeyguard() + + controller.setAlpha(0.5f) + controller.setAlpha(-1f) + + Truth.assertThat(keyguardStatusBarView.alpha).isGreaterThan(0) + Truth.assertThat(keyguardStatusBarView.alpha).isNotEqualTo(0.5f) + } + + // TODO(b/195442899): Add more tests for #updateViewState once CLs are finalized. + @Test + @DisableSceneContainer + fun updateForHeadsUp_headsUpShouldBeVisible_viewHidden() { + controller.onViewAttached() + updateStateToKeyguard() + keyguardStatusBarView.visibility = View.VISIBLE + + shadeViewStateProvider.setShouldHeadsUpBeVisible(true) + controller.updateForHeadsUp(/* animate= */ false) + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + } + + @Test + @DisableSceneContainer + fun updateForHeadsUp_headsUpShouldNotBeVisible_viewShown() { + controller.onViewAttached() + updateStateToKeyguard() + + // Start with the opposite state. + shadeViewStateProvider.setShouldHeadsUpBeVisible(true) + controller.updateForHeadsUp(/* animate= */ false) + + shadeViewStateProvider.setShouldHeadsUpBeVisible(false) + controller.updateForHeadsUp(/* animate= */ false) + + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun testNewUserSwitcherDisablesAvatar_newUiOn() = + testScope.runTest { + // GIVEN the status bar user switcher chip is enabled + kosmos.fakeUserRepository.isStatusBarUserChipEnabled = true + + // WHEN the controller is created + controller = createController() + + // THEN keyguard status bar view avatar is disabled + Truth.assertThat(keyguardStatusBarView.isKeyguardUserAvatarEnabled).isFalse() + } + + @Test + fun testNewUserSwitcherDisablesAvatar_newUiOff() { + // GIVEN the status bar user switcher chip is disabled + kosmos.fakeUserRepository.isStatusBarUserChipEnabled = false + + // WHEN the controller is created + controller = createController() + + // THEN keyguard status bar view avatar is enabled + Truth.assertThat(keyguardStatusBarView.isKeyguardUserAvatarEnabled).isTrue() + } + + @Test + fun testBlockedIcons_obeysSettingForVibrateIcon_settingOff() { + val str = mContext.getString(com.android.internal.R.string.status_bar_volume) + + // GIVEN the setting is off + Mockito.`when`(secureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0)) + .thenReturn(0) + + // WHEN CollapsedStatusBarFragment builds the blocklist + controller.updateBlockedIcons() + + // THEN status_bar_volume SHOULD be present in the list + val contains = controller.blockedIcons.contains(str) + Assert.assertTrue(contains) + } + + @Test + fun testBlockedIcons_obeysSettingForVibrateIcon_settingOn() { + val str = mContext.getString(com.android.internal.R.string.status_bar_volume) + + // GIVEN the setting is ON + Mockito.`when`( + secureSettings.getIntForUser( + Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, + 0, + UserHandle.USER_CURRENT, + ) + ) + .thenReturn(1) + + // WHEN CollapsedStatusBarFragment builds the blocklist + controller.updateBlockedIcons() + + // THEN status_bar_volume SHOULD NOT be present in the list + val contains = controller.blockedIcons.contains(str) + Assert.assertFalse(contains) + } + + private fun updateStateToNotKeyguard() { + updateStatusBarState(StatusBarState.SHADE) + } + + private fun updateStateToKeyguard() { + updateStatusBarState(StatusBarState.KEYGUARD) + } + + private fun updateStatusBarState(state: Int) { + kosmos.statusBarStateController.setState(state) + } + + @Test + @DisableSceneContainer + fun animateKeyguardStatusBarIn_isDisabled_viewStillHidden() { + controller.onViewAttached() + updateStateToKeyguard() + setDisableSystemInfo(true) + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + + controller.animateKeyguardStatusBarIn() + + // Since we're disabled, we don't actually animate in and stay invisible + Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + } + + @Test + fun animateToGlanceableHub_affectsAlpha() = + testScope.runTest { + controller.init() + val transitionAlphaAmount = .5f + ViewUtils.attachView(keyguardStatusBarView) + looper.processAllMessages() + updateStateToKeyguard() + kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal) + runCurrent() + controller.updateCommunalAlphaTransition(transitionAlphaAmount) + Truth.assertThat(keyguardStatusBarView.getAlpha()).isEqualTo(transitionAlphaAmount) + } + + @Test + fun animateToGlanceableHub_alphaResetOnCommunalNotShowing() = + testScope.runTest { + controller.init() + val transitionAlphaAmount = .5f + ViewUtils.attachView(keyguardStatusBarView) + looper.processAllMessages() + updateStateToKeyguard() + kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal) + runCurrent() + controller.updateCommunalAlphaTransition(transitionAlphaAmount) + kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank) + runCurrent() + Truth.assertThat(keyguardStatusBarView.getAlpha()).isNotEqualTo(transitionAlphaAmount) + } + + /** + * Calls [com.android.keyguard.KeyguardUpdateMonitorCallback.onFinishedGoingToSleep] to ensure + * values are updated properly. + */ + private fun onFinishedGoingToSleep() { + val keyguardUpdateCallbackCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) + Mockito.verify(keyguardUpdateMonitor) + .registerCallback(keyguardUpdateCallbackCaptor.capture()) + val callback = keyguardUpdateCallbackCaptor.value + + callback.onFinishedGoingToSleep(0) + } + + private fun setDisableSystemInfo(disabled: Boolean) { + val callback = commandQueueCallback + val disabled1 = if (disabled) StatusBarManager.DISABLE_SYSTEM_INFO else 0 + callback.disable(mContext.displayId, disabled1, 0, false) + } + + private fun setDisableSystemIcons(disabled: Boolean) { + val callback = commandQueueCallback + val disabled2 = if (disabled) StatusBarManager.DISABLE2_SYSTEM_ICONS else 0 + callback.disable(mContext.displayId, 0, disabled2, false) + } + + private val commandQueueCallback: CommandQueue.Callbacks + get() { + val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) + Mockito.verify(commandQueue).addCallback(captor.capture()) + return captor.value + } + + private fun runAllScheduled() { + backgroundExecutor.runAllReady() + fakeExecutor.runAllReady() + } + + private class TestShadeViewStateProvider : ShadeViewStateProvider { + override var panelViewExpandedHeight: Float = 100f + private var mShouldHeadsUpBeVisible = false + override var lockscreenShadeDragProgress: Float = 0f + + override fun shouldHeadsUpBeVisible(): Boolean { + return mShouldHeadsUpBeVisible + } + + fun setShouldHeadsUpBeVisible(shouldHeadsUpBeVisible: Boolean) { + this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java index 88ec18dd65f3..9099334b360c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java @@ -48,12 +48,10 @@ import com.android.keyguard.TestScopeProvider; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; -import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.data.model.StatusBarAppearance; import com.android.systemui.statusbar.data.model.StatusBarMode; -import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository; +import com.android.systemui.statusbar.data.repository.FakeStatusBarModePerDisplayRepository; import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.util.kotlin.JavaAdapter; import kotlinx.coroutines.test.TestScope; @@ -81,8 +79,8 @@ public class LightBarControllerTest extends SysuiTestCase { private SysuiDarkIconDispatcher mStatusBarIconController; private LightBarController mLightBarController; private final TestScope mTestScope = TestScopeProvider.getTestScope(); - private final FakeStatusBarModeRepository mStatusBarModeRepository = - new FakeStatusBarModeRepository(); + private final FakeStatusBarModePerDisplayRepository mStatusBarModeRepository = + new FakeStatusBarModePerDisplayRepository(); @Before public void setup() { @@ -92,15 +90,16 @@ public class LightBarControllerTest extends SysuiTestCase { mLightBarTransitionsController = mock(LightBarTransitionsController.class); when(mStatusBarIconController.getTransitionsController()).thenReturn( mLightBarTransitionsController); - mLightBarController = new LightBarController( - mContext, - new JavaAdapter(mTestScope), + mLightBarController = new LightBarControllerImpl( + mContext.getDisplayId(), + mTestScope, mStatusBarIconController, mock(BatteryController.class), mock(NavigationModeController.class), mStatusBarModeRepository, mock(DumpManager.class), - new FakeDisplayTracker(mContext)); + mTestScope.getCoroutineContext(), + mock(BiometricUnlockController.class)); mLightBarController.start(); } @@ -121,7 +120,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds) ); - mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( + mStatusBarModeRepository.getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -142,7 +141,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(0 /* appearance */, secondBounds) ); - mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( + mStatusBarModeRepository.getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -165,7 +164,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds) ); - mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( + mStatusBarModeRepository.getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -190,7 +189,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds) ); - mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( + mStatusBarModeRepository.getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -214,7 +213,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(0 /* appearance */, secondBounds) ); - mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( + mStatusBarModeRepository.getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -231,7 +230,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1)) ); - mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( + mStatusBarModeRepository.getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -249,7 +248,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(0, new Rect(0, 0, 1, 1)) ); - mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( + mStatusBarModeRepository.getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -266,7 +265,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1)) ); - mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( + mStatusBarModeRepository.getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -276,7 +275,7 @@ public class LightBarControllerTest extends SysuiTestCase { reset(mStatusBarIconController); // WHEN the same appearance regions but different status bar mode is sent - mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( + mStatusBarModeRepository.getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.LIGHTS_OUT_TRANSPARENT, STATUS_BAR_BOUNDS, @@ -298,7 +297,7 @@ public class LightBarControllerTest extends SysuiTestCase { /* start= */ new Rect(0, 0, 10, 10), /* end= */ new Rect(0, 0, 20, 20)); - mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( + mStatusBarModeRepository.getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, startingBounds, @@ -311,7 +310,7 @@ public class LightBarControllerTest extends SysuiTestCase { BoundsPair newBounds = new BoundsPair( /* start= */ new Rect(0, 0, 30, 30), /* end= */ new Rect(0, 0, 40, 40)); - mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( + mStatusBarModeRepository.getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, newBounds, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt index e0d9fac0eba5..110dec6c33aa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt @@ -36,7 +36,6 @@ import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R @@ -58,7 +57,6 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent -import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -144,6 +142,7 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { controller.start() controller.addCallback(mockOngoingCallListener) controller.setChipView(chipView) + onTeardown { controller.tearDownChipView() } val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java) verify(notificationCollection).addCollectionListener(collectionListenerCaptor.capture()) @@ -153,11 +152,6 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { .thenReturn(PROC_STATE_INVISIBLE) } - @After - fun tearDown() { - controller.tearDownChipView() - } - @Test fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() { val notification = NotificationEntryBuilder(createOngoingCallNotifEntry()) @@ -224,7 +218,7 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { notifCollectionListener.onEntryUpdated(notification.build()) chipView.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), ) assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) @@ -241,7 +235,7 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { notifCollectionListener.onEntryUpdated(notification.build()) chipView.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), ) assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) @@ -257,7 +251,7 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { notifCollectionListener.onEntryUpdated(notification.build()) chipView.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), ) assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) @@ -668,7 +662,7 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { private fun createCallNotifEntry( callStyle: Notification.CallStyle, - nullContentIntent: Boolean = false + nullContentIntent: Boolean = false, ): NotificationEntry { val notificationEntryBuilder = NotificationEntryBuilder() notificationEntryBuilder.modifyNotification(context).style = callStyle diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt index fc1ea227f1bd..19d5a16deabd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt @@ -66,6 +66,7 @@ class DeviceBasedSatelliteRepositorySwitcherTest : SysuiTestCase() { logBuffer = FakeLogBuffer.Factory.create(), verboseLogBuffer = FakeLogBuffer.Factory.create(), systemClock, + context.resources, ) private val demoDataSource = mock<DemoDeviceBasedSatelliteDataSource>().also { @@ -80,7 +81,11 @@ class DeviceBasedSatelliteRepositorySwitcherTest : SysuiTestCase() { ) } private val demoImpl = - DemoDeviceBasedSatelliteRepository(demoDataSource, testScope.backgroundScope) + DemoDeviceBasedSatelliteRepository( + demoDataSource, + testScope.backgroundScope, + context.resources, + ) private val underTest = DeviceBasedSatelliteRepositorySwitcher( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt index 87693891a281..a70881aedeb6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt @@ -58,7 +58,12 @@ class DemoDeviceBasedSatelliteRepositoryTest : SysuiTestCase() { whenever(it.satelliteEvents).thenReturn(fakeSatelliteEvents) } - underTest = DemoDeviceBasedSatelliteRepository(dataSource, testScope.backgroundScope) + underTest = + DemoDeviceBasedSatelliteRepository( + dataSource, + testScope.backgroundScope, + context.resources, + ) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt index 55460bd5b801..41fa9e7be81b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt @@ -28,4 +28,6 @@ class FakeDeviceBasedSatelliteRepository() : DeviceBasedSatelliteRepository { override val signalStrength = MutableStateFlow(0) override val isSatelliteAllowedForCurrentLocation = MutableStateFlow(false) + + override var isOpportunisticSatelliteIconEnabled: Boolean = true } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt index e7e496938033..509aa7ad67fd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt @@ -172,6 +172,26 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { } @Test + fun icon_null_allOosAndConfigIsFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.icon) + + // GIVEN config for opportunistic icon is false + repo.isOpportunisticSatelliteIconEnabled = false + + // GIVEN all icons are OOS + val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1) + i1.isInService.value = false + i1.isEmergencyOnly.value = false + + // GIVEN apm is disabled + airplaneModeRepository.setIsAirplaneMode(false) + + // THEN icon is null because it is not allowed + assertThat(latest).isNull() + } + + @Test fun icon_null_isEmergencyOnly() = testScope.runTest { val latest by collectLastValue(underTest.icon) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 53e033ef7c93..e7ca1dd3e4b7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -51,6 +51,8 @@ import android.graphics.Color; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import androidx.annotation.VisibleForTesting; @@ -561,6 +563,113 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { } @Test + @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI) + public void onWallpaperColorsChanged_homeWallpaper_shouldUpdateTheme() { + // Should ask for a new theme when the colors of the last applied wallpaper change + WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(Color.BLUE), null); + + String jsonString = + "{\"android.theme.customization.color_source\":\"home_wallpaper\"," + + "\"android.theme.customization.system_palette\":\"A16B00\"," + + "\"android.theme.customization.accent_color\":\"A16B00\"," + + "\"android.theme.customization.color_index\":\"2\"}"; + + when(mSecureSettings.getStringForUser( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt())) + .thenReturn(jsonString); + when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM)) + .thenReturn(1); + // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on + // latest wallpaper + when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM)) + .thenReturn(2); + + mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM, + USER_SYSTEM); + + ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class); + verify(mSecureSettings).putStringForUser( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture(), + anyInt()); + + verify(mThemeOverlayApplier) + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); + } + + + + @Test + @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI) + public void onWallpaperColorsChanged_homeWallpaperWithSameColor_shouldKeepThemeAndReapply() { + // Shouldn't ask for a new theme when the colors of the last applied wallpaper change + // with the same specified system palette one. + WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(0xffa16b00), null); + + String jsonString = + "{\"android.theme.customization.color_source\":\"home_wallpaper\"," + + "\"android.theme.customization.system_palette\":\"A16B00\"," + + "\"android.theme.customization.accent_color\":\"A16B00\"," + + "\"android.theme.customization.color_index\":\"2\"}"; + + when(mSecureSettings.getStringForUser( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt())) + .thenReturn(jsonString); + when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM)) + .thenReturn(1); + // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on + // latest wallpaper + when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM)) + .thenReturn(2); + + mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM, + USER_SYSTEM); + + ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class); + verify(mSecureSettings, never()).putString( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture()); + + // Apply overlay by existing theme from secure setting + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); + } + + @Test + @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI) + public void onWallpaperColorsChanged_lockWallpaper_shouldKeepTheme() { + // Should ask for a new theme when the colors of the last applied wallpaper change + WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(Color.BLUE), null); + + String jsonString = + "{\"android.theme.customization.color_source\":\"home_wallpaper\"," + + "\"android.theme.customization.system_palette\":\"A16B00\"," + + "\"android.theme.customization.accent_color\":\"A16B00\"," + + "\"android.theme.customization.color_index\":\"2\"}"; + + when(mSecureSettings.getStringForUser( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt())) + .thenReturn(jsonString); + when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM)) + .thenReturn(1); + // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on + // latest wallpaper + when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM)) + .thenReturn(2); + + mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_LOCK, + USER_SYSTEM); + + ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class); + verify(mSecureSettings, never()).putString( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture()); + + verify(mThemeOverlayApplier, never()) + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); + } + + @Test + @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI) public void onWallpaperColorsChanged_resetThemeWhenFromLatestWallpaper() { // Should ask for a new theme when the colors of the last applied wallpaper change WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), @@ -594,6 +703,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { } @Test + @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI) public void onWallpaperColorsChanged_keepThemeWhenFromLatestWallpaperAndSpecifiedColor() { // Shouldn't ask for a new theme when the colors of the last applied wallpaper change // with the same specified system palette one. @@ -627,6 +737,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { } @Test + @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI) public void onWallpaperColorsChanged_keepThemeIfNotLatestWallpaper() { // Shouldn't ask for a new theme when the colors of the wallpaper that is not the last // applied one change diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt index a0cfab4d2160..e88dbd27fd37 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt @@ -22,11 +22,10 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -42,70 +41,127 @@ import org.junit.runner.RunWith class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() { private val kosmos = testKosmos() - private val dispatcher = kosmos.testDispatcher private val testScope = kosmos.testScope private val secureSettings = kosmos.fakeSettings - private val userRepository = Kosmos().fakeUserRepository - private lateinit var repository: UserAwareSecureSettingsRepository + private val userRepository = kosmos.fakeUserRepository + private lateinit var underTest: UserAwareSecureSettingsRepository @Before fun setup() { - repository = - UserAwareSecureSettingsRepositoryImpl( - secureSettings, - userRepository, - dispatcher, - ) + underTest = kosmos.userAwareSecureSettingsRepository + userRepository.setUserInfos(USER_INFOS) - setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER) - setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER) + + secureSettings.putBoolForUser(BOOL_SETTING_NAME, true, USER_1.id) + secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_2.id) + secureSettings.putIntForUser(INT_SETTING_NAME, 1337, USER_1.id) + secureSettings.putIntForUser(INT_SETTING_NAME, 818, USER_2.id) } @Test - fun settingEnabledEmitsValueForCurrentUser() { + fun boolSetting_emitsInitialValue() { testScope.runTest { - userRepository.setSelectedUserInfo(SETTING_ENABLED_USER) + userRepository.setSelectedUserInfo(USER_1) - val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME)) + val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false)) assertThat(enabled).isTrue() } } @Test - fun settingEnabledEmitsNewValueWhenSettingChanges() { + fun boolSetting_whenSettingChanges_emitsNewValue() { testScope.runTest { - userRepository.setSelectedUserInfo(SETTING_ENABLED_USER) - val enabled by collectValues(repository.boolSettingForActiveUser(SETTING_NAME)) + userRepository.setSelectedUserInfo(USER_1) + val enabled by collectValues(underTest.boolSetting(BOOL_SETTING_NAME, false)) runCurrent() - setSettingValueForUser(enabled = false, userInfo = SETTING_ENABLED_USER) + secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_1.id) assertThat(enabled).containsExactly(true, false).inOrder() } } @Test - fun settingEnabledEmitsValueForNewUserWhenUserChanges() { + fun boolSetting_whenWhenUserChanges_emitsNewValue() { testScope.runTest { - userRepository.setSelectedUserInfo(SETTING_ENABLED_USER) - val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME)) + userRepository.setSelectedUserInfo(USER_1) + val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false)) runCurrent() - userRepository.setSelectedUserInfo(SETTING_DISABLED_USER) + userRepository.setSelectedUserInfo(USER_2) assertThat(enabled).isFalse() } } - private fun setSettingValueForUser(enabled: Boolean, userInfo: UserInfo) { - secureSettings.putBoolForUser(SETTING_NAME, enabled, userInfo.id) + @Test + fun intSetting_emitsInitialValue() { + testScope.runTest { + userRepository.setSelectedUserInfo(USER_1) + + val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0)) + + assertThat(number).isEqualTo(1337) + } + } + + @Test + fun intSetting_whenSettingChanges_emitsNewValue() { + testScope.runTest { + userRepository.setSelectedUserInfo(USER_1) + val number by collectValues(underTest.intSetting(INT_SETTING_NAME, 0)) + runCurrent() + + secureSettings.putIntForUser(INT_SETTING_NAME, 1338, USER_1.id) + + assertThat(number).containsExactly(1337, 1338).inOrder() + } + } + + @Test + fun intSetting_whenWhenUserChanges_emitsNewValue() { + testScope.runTest { + userRepository.setSelectedUserInfo(USER_1) + val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0)) + runCurrent() + + userRepository.setSelectedUserInfo(USER_2) + + assertThat(number).isEqualTo(818) + } } + @Test + fun getInt_returnsInitialValue() = + testScope.runTest { + userRepository.setSelectedUserInfo(USER_1) + + assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(1337) + } + + @Test + fun getInt_whenSettingChanges_returnsNewValue() = + testScope.runTest { + userRepository.setSelectedUserInfo(USER_1) + secureSettings.putIntForUser(INT_SETTING_NAME, 999, USER_1.id) + + assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(999) + } + + @Test + fun getInt_whenUserChanges_returnsThatUserValue() = + testScope.runTest { + userRepository.setSelectedUserInfo(USER_2) + + assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(818) + } + private companion object { - const val SETTING_NAME = "SETTING_NAME" - val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0) - val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0) - val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER) + const val BOOL_SETTING_NAME = "BOOL_SETTING_NAME" + const val INT_SETTING_NAME = "INT_SETTING_NAME" + val USER_1 = UserInfo(/* id= */ 0, "user1", /* flags= */ 0) + val USER_2 = UserInfo(/* id= */ 1, "user2", /* flags= */ 0) + val USER_INFOS = listOf(USER_1, USER_2) } } 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/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 d4a52c3aeafb..c8ef093c40db 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -383,6 +383,10 @@ <!-- Whether to show activity indicators in the status bar --> <bool name="config_showActivity">false</bool> + <!-- Whether to show the opportunistic satellite icon. When true, an icon will show to indicate + satellite capabilities when all other connections are out of service. --> + <bool name="config_showOpportunisticSatelliteIcon">true</bool> + <!-- Whether or not to show the notification shelf that houses the icons of notifications that have been scrolled off-screen. --> <bool name="config_showNotificationShelf">true</bool> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 1766cdf8c804..7b50582efe64 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1518,7 +1518,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/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..eda07cfe8d91 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,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @see Intent#ACTION_USER_UNLOCKED */ public boolean isUserUnlocked(int userId) { - return mUserIsUnlocked.get(userId); + if (Flags.userEncryptedSource()) { + boolean userStorageUnlocked = mUserManager.isUserUnlocked(userId); + mLogger.logUserStorageUnlocked(userId, userStorageUnlocked); + return userStorageUnlocked; + } else { + return mUserIsUnlocked.get(userId); + } } /** @@ -4213,7 +4221,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/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 12fc3c262367..b3ddde38509a 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -582,6 +582,18 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { logBuffer.log(TAG, DEBUG, { int1 = userId }, { "userUnlocked userId: $int1" }) } + fun logUserStorageUnlocked(userId: Int, result: Boolean) { + logBuffer.log( + TAG, + DEBUG, + { + int1 = userId + bool1 = result + }, + { "Invoked UserManager#isUserUnlocked $int1, result: $bool1" }, + ) + } + fun logUserStopped(userId: Int, isUnlocked: Boolean) { logBuffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt index 5414b623ff97..39fd44a83c58 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt @@ -63,6 +63,7 @@ constructor( private val captioningManager: StateFlow<CaptioningManager?> = userRepository.selectedUser .map { userScopedCaptioningManagerProvider.forUser(it.userInfo.userHandle) } + .flowOn(backgroundCoroutineContext) .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null) override val captioningModel: StateFlow<CaptioningModel?> = diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 12b5fc01845c..b491c94db151 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -27,6 +27,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AlertDialog; +import android.app.KeyguardManager; import android.content.Context; import android.content.res.Configuration; import android.graphics.PixelFormat; @@ -315,6 +316,16 @@ public class AuthContainerView extends LinearLayout mBiometricCallback = new BiometricCallback(); mMSDLPlayer = msdlPlayer; + // Listener for when device locks from adaptive auth, dismiss prompt + getContext().getSystemService(KeyguardManager.class).addKeyguardLockedStateListener( + getContext().getMainExecutor(), + isKeyguardLocked -> { + if (isKeyguardLocked) { + onStartedGoingToSleep(); + } + } + ); + final BiometricModalities biometricModalities = new BiometricModalities( Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds), Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds)); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt index ba51d02fd288..68ec0f2d57ef 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt @@ -25,6 +25,7 @@ import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback +import android.util.Log import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.biometrics.shared.model.toSensorStrength @@ -91,13 +92,14 @@ constructor( trySendWithFailureLogging( DEFAULT_PROPS, TAG, - "no registered sensors, use default props" + "no registered sensors, use default props", ) } else { + Log.d(TAG, "onAllAuthenticatorsRegistered $sensors") trySendWithFailureLogging( sensors[0], TAG, - "update properties on authenticators registered" + "update properties on authenticators registered", ) } } @@ -160,7 +162,7 @@ constructor( FingerprintSensorProperties.TYPE_UNKNOWN, false /* halControlsIllumination */, true /* resetLockoutRequiresHardwareAuthToken */, - listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT) + listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT), ) private val DEFAULT_PROPS = FingerprintSensorPropertiesInternal( @@ -171,7 +173,7 @@ constructor( FingerprintSensorProperties.TYPE_UNKNOWN, false /* halControlsIllumination */, true /* resetLockoutRequiresHardwareAuthToken */, - listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT) + listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt index 18a7739f12ab..abbbd730c47e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt @@ -48,14 +48,14 @@ constructor( private val authController: AuthController, private val selectedUserInteractor: SelectedUserInteractor, private val fingerprintManager: FingerprintManager?, - @Application scope: CoroutineScope + @Application scope: CoroutineScope, ) { private fun calculateIconSize(): Int { val pixelPitch = context.resources.getFloat(R.dimen.pixel_pitch) if (pixelPitch <= 0) { Log.e( "UdfpsOverlayInteractor", - "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device." + "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device.", ) } return (context.resources.getFloat(R.dimen.udfps_icon_size) / pixelPitch).toInt() @@ -83,12 +83,11 @@ constructor( /** Sets whether Udfps overlay should handle touches */ fun setHandleTouches(shouldHandle: Boolean = true) { - if (authController.isUdfpsSupported - && shouldHandle != _shouldHandleTouches.value) { + if (authController.isUdfpsSupported && shouldHandle != _shouldHandleTouches.value) { fingerprintManager?.setIgnoreDisplayTouches( requestId.value, authController.udfpsProps!!.get(0).sensorId, - !shouldHandle + !shouldHandle, ) } _shouldHandleTouches.value = shouldHandle @@ -107,10 +106,11 @@ constructor( override fun onUdfpsLocationChanged( udfpsOverlayParams: UdfpsOverlayParams ) { + Log.d(TAG, "udfpsOverlayParams updated $udfpsOverlayParams") trySendWithFailureLogging( udfpsOverlayParams, TAG, - "update udfpsOverlayParams" + "update udfpsOverlayParams", ) } } 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/clipboardoverlay/ClipboardIndicationProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt new file mode 100644 index 000000000000..da94d5b518b7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt @@ -0,0 +1,26 @@ +/* + * 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 + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +@SysUISingleton +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/common/data/CommonDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt index d1c728cd74fa..19238804fb12 100644 --- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt @@ -18,19 +18,12 @@ package com.android.systemui.common.data import com.android.systemui.common.data.repository.PackageChangeRepository import com.android.systemui.common.data.repository.PackageChangeRepositoryImpl -import com.android.systemui.common.ui.data.repository.ConfigurationRepository -import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl import dagger.Binds import dagger.Module @Module abstract class CommonDataLayerModule { @Binds - abstract fun bindConfigurationRepository( - impl: ConfigurationRepositoryImpl - ): ConfigurationRepository - - @Binds abstract fun bindPackageChangeRepository( impl: PackageChangeRepositoryImpl ): PackageChangeRepository diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt index b36da3bfcd26..a3735f95cf74 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt @@ -28,7 +28,7 @@ import javax.inject.Qualifier /** * Annotates elements that provide information from the global configuration. * - * The global configuration is the one associted with the main display. Secondary displays will + * The global configuration is the one associated with the main display. Secondary displays will * apply override to the global configuration. Elements annotated with this shouldn't be used for * secondary displays. */ diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt index 2052c70e740d..df891523798f 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt @@ -23,13 +23,17 @@ import android.view.DisplayInfo import androidx.annotation.DimenRes import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.common.ui.GlobalConfig import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.wrapper.DisplayUtilsWrapper import dagger.Binds import dagger.Module -import javax.inject.Inject +import dagger.Provides +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose @@ -57,66 +61,62 @@ interface ConfigurationRepository { fun getDimensionPixelSize(id: Int): Int } -@SysUISingleton class ConfigurationRepositoryImpl -@Inject +@AssistedInject constructor( - private val configurationController: ConfigurationController, - private val context: Context, + @Assisted private val configurationController: ConfigurationController, + @Assisted private val context: Context, @Application private val scope: CoroutineScope, private val displayUtils: DisplayUtilsWrapper, ) : ConfigurationRepository { private val displayInfo = MutableStateFlow(DisplayInfo()) - override val onAnyConfigurationChange: Flow<Unit> = - conflatedCallbackFlow { - val callback = - object : ConfigurationController.ConfigurationListener { - override fun onUiModeChanged() { - sendUpdate("ConfigurationRepository#onUiModeChanged") - } - - override fun onThemeChanged() { - sendUpdate("ConfigurationRepository#onThemeChanged") - } - - override fun onConfigChanged(newConfig: Configuration) { - sendUpdate("ConfigurationRepository#onConfigChanged") - } - - fun sendUpdate(reason: String) { - trySendWithFailureLogging(Unit, reason) - } + override val onAnyConfigurationChange: Flow<Unit> = conflatedCallbackFlow { + val callback = + object : ConfigurationController.ConfigurationListener { + override fun onUiModeChanged() { + sendUpdate("ConfigurationRepository#onUiModeChanged") } - configurationController.addCallback(callback) - awaitClose { configurationController.removeCallback(callback) } - } - override val onConfigurationChange: Flow<Unit> = - conflatedCallbackFlow { - val callback = - object : ConfigurationController.ConfigurationListener { - override fun onConfigChanged(newConfig: Configuration) { - trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged") - } + override fun onThemeChanged() { + sendUpdate("ConfigurationRepository#onThemeChanged") } - configurationController.addCallback(callback) - awaitClose { configurationController.removeCallback(callback) } - } - override val configurationValues: Flow<Configuration> = - conflatedCallbackFlow { - val callback = - object : ConfigurationController.ConfigurationListener { - override fun onConfigChanged(newConfig: Configuration) { - trySend(newConfig) - } - } - - trySend(context.resources.configuration) - configurationController.addCallback(callback) - awaitClose { configurationController.removeCallback(callback) } + override fun onConfigChanged(newConfig: Configuration) { + sendUpdate("ConfigurationRepository#onConfigChanged") + } + + fun sendUpdate(reason: String) { + trySendWithFailureLogging(Unit, reason) + } + } + configurationController.addCallback(callback) + awaitClose { configurationController.removeCallback(callback) } + } + + override val onConfigurationChange: Flow<Unit> = conflatedCallbackFlow { + val callback = + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration) { + trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged") + } } + configurationController.addCallback(callback) + awaitClose { configurationController.removeCallback(callback) } + } + + override val configurationValues: Flow<Configuration> = conflatedCallbackFlow { + val callback = + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration) { + trySend(newConfig) + } + } + + trySend(context.resources.configuration) + configurationController.addCallback(callback) + awaitClose { configurationController.removeCallback(callback) } + } override val scaleForResolution: StateFlow<Float> = onConfigurationChange @@ -134,7 +134,7 @@ constructor( maxDisplayMode.physicalWidth, maxDisplayMode.physicalHeight, displayInfo.value.naturalWidth, - displayInfo.value.naturalHeight + displayInfo.value.naturalHeight, ) return if (scaleFactor == Float.POSITIVE_INFINITY) 1f else scaleFactor } @@ -144,9 +144,40 @@ constructor( override fun getDimensionPixelSize(@DimenRes id: Int): Int { return context.resources.getDimensionPixelSize(id) } + + @AssistedFactory + interface Factory { + fun create( + context: Context, + configurationController: ConfigurationController, + ): ConfigurationRepositoryImpl + } } @Module -interface ConfigurationRepositoryModule { - @Binds fun bindImpl(impl: ConfigurationRepositoryImpl): ConfigurationRepository +abstract class ConfigurationRepositoryModule { + + /** + * For compatibility reasons. Ideally, only the an annotated [ConfigurationRepository] should be + * injected. + */ + @Binds + @Deprecated("Use the ConfigurationRepository annotated with @GlobalConfig instead.") + @SysUISingleton + abstract fun provideDefaultConfigRepository( + @GlobalConfig configurationRepository: ConfigurationRepository + ): ConfigurationRepository + + companion object { + @Provides + @GlobalConfig + @SysUISingleton + fun provideGlobalConfigRepository( + context: Context, + @GlobalConfig configurationController: ConfigurationController, + factory: ConfigurationRepositoryImpl.Factory, + ): ConfigurationRepository { + return factory.create(context, configurationController) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt index adb1ee2b22ee..eb423d69a4eb 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onStart /** Business logic related to configuration changes. */ +// TODO: b/374267505 - Create a @ShadeDisplayWindow annotated version of this. @SysUISingleton class ConfigurationInteractor @Inject constructor(private val repository: ConfigurationRepository) { /** diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt index 012c844586bc..b80e77ce5b2e 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt @@ -19,13 +19,13 @@ package com.android.systemui.communal.smartspace import android.app.smartspace.SmartspaceConfig import android.app.smartspace.SmartspaceManager import android.app.smartspace.SmartspaceSession -import android.content.Context import android.util.Log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB +import com.android.systemui.settings.UserTracker import com.android.systemui.smartspace.SmartspacePrecondition import com.android.systemui.smartspace.SmartspaceTargetFilter import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN @@ -42,8 +42,7 @@ import javax.inject.Named class CommunalSmartspaceController @Inject constructor( - private val context: Context, - private val smartspaceManager: SmartspaceManager?, + private val userTracker: UserTracker, private val execution: Execution, @Main private val uiExecutor: Executor, @Named(LOCKSCREEN_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition, @@ -55,6 +54,7 @@ constructor( private const val TAG = "CommunalSmartspaceCtrlr" } + private var userSmartspaceManager: SmartspaceManager? = null private var session: SmartspaceSession? = null private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null) private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null) @@ -104,7 +104,11 @@ constructor( } private fun connectSession() { - if (smartspaceManager == null) { + if (userSmartspaceManager == null) { + userSmartspaceManager = + userTracker.userContext.getSystemService(SmartspaceManager::class.java) + } + if (userSmartspaceManager == null) { return } if (plugin == null) { @@ -119,11 +123,11 @@ constructor( } val newSession = - smartspaceManager.createSmartspaceSession( - SmartspaceConfig.Builder(context, UI_SURFACE_GLANCEABLE_HUB).build() + userSmartspaceManager?.createSmartspaceSession( + SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_GLANCEABLE_HUB).build() ) Log.d(TAG, "Starting smartspace session for communal") - newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener) + newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener) this.session = newSession plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) } @@ -163,7 +167,7 @@ constructor( private fun addAndRegisterListener( listener: SmartspaceTargetListener, - smartspaceDataPlugin: BcSmartspaceDataPlugin? + smartspaceDataPlugin: BcSmartspaceDataPlugin?, ) { execution.assertIsMainThread() smartspaceDataPlugin?.registerListener(listener) @@ -174,7 +178,7 @@ constructor( private fun removeAndUnregisterListener( listener: SmartspaceTargetListener, - smartspaceDataPlugin: BcSmartspaceDataPlugin? + smartspaceDataPlugin: BcSmartspaceDataPlugin?, ) { execution.assertIsMainThread() smartspaceDataPlugin?.unregisterListener(listener) 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/communal/widgets/GlanceableHubWidgetManager.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt index 202edf71fa11..2f686fd91ede 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt @@ -19,7 +19,10 @@ package com.android.systemui.communal.widgets import android.appwidget.AppWidgetHost.AppWidgetHostListener import android.appwidget.AppWidgetProviderInfo import android.content.ComponentName +import android.content.IntentSender import android.os.IBinder +import android.os.OutcomeReceiver +import android.os.RemoteException import android.os.UserHandle import android.widget.RemoteViews import com.android.server.servicewatcher.ServiceWatcher @@ -27,14 +30,19 @@ import com.android.server.servicewatcher.ServiceWatcher.ServiceListener import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IAppWidgetHostListener +import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import java.util.concurrent.Executor import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.launch /** * Manages updates to Glanceable Hub widgets and requests to edit them from the headless system @@ -47,6 +55,8 @@ import kotlinx.coroutines.channels.awaitClose class GlanceableHubWidgetManager @Inject constructor( + @Background private val bgExecutor: Executor, + @Background private val bgScope: CoroutineScope, glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper, @CommunalLog logBuffer: LogBuffer, serviceWatcherFactory: ServiceWatcherFactory<GlanceableHubWidgetManagerServiceInfo?>, @@ -101,8 +111,7 @@ constructor( rank: Int?, configurator: WidgetConfigurator?, ) = runOnService { service -> - // TODO(b/375036327): Add support for widget configuration - service.addWidget(provider, user, rank ?: -1) + service.addWidget(provider, user, rank ?: -1, createIConfigureWidgetCallback(configurator)) } /** Requests the foreground user to delete a widget. */ @@ -129,18 +138,54 @@ constructor( ) } - private fun runOnService(block: (IGlanceableHubWidgetManagerService) -> Unit) { - serviceWatcher.runOnBinder( - object : ServiceWatcher.BinderOperation { - override fun run(binder: IBinder?) { - block(IGlanceableHubWidgetManagerService.Stub.asInterface(binder)) + /** + * Requests the foreground user for the [IntentSender] to start a configuration activity for the + * widget. + * + * @param appWidgetId Id of the widget to configure. + * @param outcomeReceiver Callback for receiving the result or error. + * @param executor Executor to run the callback on. + */ + fun getIntentSenderForConfigureActivity( + appWidgetId: Int, + outcomeReceiver: OutcomeReceiver<IntentSender?, Throwable>, + executor: Executor, + ) { + bgExecutor.execute { + serviceWatcher.runOnBinder( + object : ServiceWatcher.BinderOperation { + override fun run(binder: IBinder?) { + val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder) + try { + val result = service.getIntentSenderForConfigureActivity(appWidgetId) + executor.execute { outcomeReceiver.onResult(result) } + } catch (e: RemoteException) { + executor.execute { outcomeReceiver.onError(e) } + } + } + + override fun onError(t: Throwable?) { + t?.let { executor.execute { outcomeReceiver.onError(t) } } + } } + ) + } + } - override fun onError(t: Throwable?) { - // TODO(b/375236794): handle failure in case service is unbound + private fun runOnService(block: (IGlanceableHubWidgetManagerService) -> Unit) { + bgExecutor.execute { + serviceWatcher.runOnBinder( + object : ServiceWatcher.BinderOperation { + override fun run(binder: IBinder?) { + block(IGlanceableHubWidgetManagerService.Stub.asInterface(binder)) + } + + override fun onError(t: Throwable?) { + // TODO(b/375236794): handle failure in case service is unbound + } } - } - ) + ) + } } private fun createIAppWidgetHostListener( @@ -165,6 +210,30 @@ constructor( } } + private fun createIConfigureWidgetCallback( + configurator: WidgetConfigurator? + ): IConfigureWidgetCallback? { + return configurator?.let { + object : IConfigureWidgetCallback.Stub() { + override fun onConfigureWidget( + appWidgetId: Int, + resultReceiver: IConfigureWidgetCallback.IResultReceiver?, + ) { + bgScope.launch { + val success = configurator.configureWidget(appWidgetId) + try { + resultReceiver?.onResult(success) + } catch (e: RemoteException) { + logger.e({ "Error reporting widget configuration result: $str1" }) { + str1 = e.localizedMessage + } + } + } + } + } + } + } + companion object { private const val TAG = "GlanceableHubWidgetManager" } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt index 4d042fc277de..0e43e2aa4c14 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt @@ -20,8 +20,10 @@ import android.appwidget.AppWidgetHost.AppWidgetHostListener import android.appwidget.AppWidgetProviderInfo import android.content.ComponentName import android.content.Intent +import android.content.IntentSender import android.os.IBinder import android.os.RemoteCallbackList +import android.os.RemoteException import android.os.UserHandle import android.widget.RemoteViews import androidx.lifecycle.LifecycleService @@ -29,11 +31,13 @@ import androidx.lifecycle.lifecycleScope import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IAppWidgetHostListener +import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog import javax.inject.Inject +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Job import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -98,7 +102,15 @@ constructor( val job = widgetRepository.communalWidgets - .onEach { widgets -> listener.onWidgetsUpdated(widgets) } + .onEach { widgets -> + try { + listener.onWidgetsUpdated(widgets) + } catch (e: RemoteException) { + logger.e({ "Error pushing widget update: $str1" }) { + str1 = e.localizedMessage + } + } + } .launchIn(lifecycleScope) widgetListenersRegistry.register(listener, job) } @@ -122,7 +134,12 @@ constructor( appWidgetHost.setListener(appWidgetId, createListener(listener)) } - private fun addWidgetInternal(provider: ComponentName?, user: UserHandle?, rank: Int) { + private fun addWidgetInternal( + provider: ComponentName?, + user: UserHandle?, + rank: Int, + callback: IConfigureWidgetCallback?, + ) { if (provider == null) { throw IllegalStateException("Provider cannot be null") } @@ -131,8 +148,29 @@ constructor( throw IllegalStateException("User cannot be null") } - // TODO(b/375036327): Add support for widget configuration - widgetRepository.addWidget(provider, user, rank, configurator = null) + val configurator = + callback?.let { + WidgetConfigurator { appWidgetId -> + try { + val result = CompletableDeferred<Boolean>() + val resultReceiver = + object : IConfigureWidgetCallback.IResultReceiver.Stub() { + override fun onResult(success: Boolean) { + result.complete(success) + } + } + + callback.onConfigureWidget(appWidgetId, resultReceiver) + result.await() + } catch (e: RemoteException) { + logger.e({ "Error configuring widget: $str1" }) { + str1 = e.localizedMessage + } + false + } + } + } + widgetRepository.addWidget(provider, user, rank, configurator) } private fun deleteWidgetInternal(appWidgetId: Int) { @@ -168,22 +206,55 @@ constructor( widgetRepository.resizeWidget(appWidgetId, spanY, appWidgetIds.zip(ranks).toMap()) } + private fun getIntentSenderForConfigureActivityInternal(appWidgetId: Int): IntentSender? { + return try { + appWidgetHost.getIntentSenderForConfigureActivity(appWidgetId, /* intentFlags= */ 0) + } catch (e: IntentSender.SendIntentException) { + logger.e({ "Error getting intent sender for configure activity" }) { + str1 = e.localizedMessage + } + null + } + } + private fun createListener(listener: IAppWidgetHostListener): AppWidgetHostListener { return object : AppWidgetHostListener { override fun onUpdateProviderInfo(appWidget: AppWidgetProviderInfo?) { - listener.onUpdateProviderInfo(appWidget) + try { + listener.onUpdateProviderInfo(appWidget) + } catch (e: RemoteException) { + logger.e({ "Error pushing on update provider info: $str1" }) { + str1 = e.localizedMessage + } + } } override fun updateAppWidget(views: RemoteViews?) { - listener.updateAppWidget(views) + try { + listener.updateAppWidget(views) + } catch (e: RemoteException) { + logger.e({ "Error updating app widget: $str1" }) { str1 = e.localizedMessage } + } } override fun updateAppWidgetDeferred(packageName: String?, appWidgetId: Int) { - listener.updateAppWidgetDeferred(packageName, appWidgetId) + try { + listener.updateAppWidgetDeferred(packageName, appWidgetId) + } catch (e: RemoteException) { + logger.e({ "Error updating app widget deferred: $str1" }) { + str1 = e.localizedMessage + } + } } override fun onViewDataChanged(viewId: Int) { - listener.onViewDataChanged(viewId) + try { + listener.onViewDataChanged(viewId) + } catch (e: RemoteException) { + logger.e({ "Error pushing on view data changed: $str1" }) { + str1 = e.localizedMessage + } + } } } } @@ -219,11 +290,16 @@ constructor( } } - override fun addWidget(provider: ComponentName?, user: UserHandle?, rank: Int) { + override fun addWidget( + provider: ComponentName?, + user: UserHandle?, + rank: Int, + callback: IConfigureWidgetCallback?, + ) { val iden = clearCallingIdentity() try { - addWidgetInternal(provider, user, rank) + addWidgetInternal(provider, user, rank, callback) } finally { restoreCallingIdentity(iden) } @@ -263,6 +339,16 @@ constructor( restoreCallingIdentity(iden) } } + + override fun getIntentSenderForConfigureActivity(appWidgetId: Int): IntentSender? { + val iden = clearCallingIdentity() + + try { + return getIntentSenderForConfigureActivityInternal(appWidgetId) + } finally { + restoreCallingIdentity(iden) + } + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl b/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl index e556472c78a7..d71b2303db30 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl @@ -2,6 +2,7 @@ package com.android.systemui.communal.widgets; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; +import android.content.IntentSender; import android.os.UserHandle; import android.widget.RemoteViews; import com.android.systemui.communal.shared.model.CommunalWidgetContentModel; @@ -21,7 +22,8 @@ interface IGlanceableHubWidgetManagerService { oneway void setAppWidgetHostListener(int appWidgetId, in IAppWidgetHostListener listener); // Requests to add a widget in the Glanceable Hub. - oneway void addWidget(in ComponentName provider, in UserHandle user, int rank); + oneway void addWidget(in ComponentName provider, in UserHandle user, int rank, + in IConfigureWidgetCallback callback); // Requests to delete a widget from the Glanceable Hub. oneway void deleteWidget(int appWidgetId); @@ -32,6 +34,9 @@ interface IGlanceableHubWidgetManagerService { // Requests to resize a widget in the Glanceable Hub. oneway void resizeWidget(int appWidgetId, int spanY, in int[] appWidgetIds, in int[] ranks); + // Returns the [IntentSender] for launching the configuration activity of the given widget. + IntentSender getIntentSenderForConfigureActivity(int appWidgetId); + // Listener for Glanceable Hub widget updates oneway interface IGlanceableHubWidgetsListener { // Called when widgets have updated. @@ -48,4 +53,15 @@ interface IGlanceableHubWidgetManagerService { void onViewDataChanged(int viewId); } + + oneway interface IConfigureWidgetCallback { + // Called when the given widget should launch its configuration activity. The caller should + // report the result through the [IResultReceiver]. + void onConfigureWidget(int appWidgetId, in IResultReceiver resultReceiver); + + interface IResultReceiver { + // Called when the widget configuration operation returns a result. + void onResult(boolean success); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt index d157cd7acb76..fddec5659b96 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt @@ -18,16 +18,19 @@ package com.android.systemui.communal.widgets import android.app.Activity import android.app.ActivityOptions -import android.content.ActivityNotFoundException +import android.content.IntentSender +import android.os.OutcomeReceiver import android.window.SplashScreen import androidx.activity.ComponentActivity import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.util.nullableAtomicReference import dagger.Lazy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import java.util.concurrent.Executor import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext @@ -43,12 +46,22 @@ constructor( private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>, @Background private val bgDispatcher: CoroutineDispatcher, private val glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper, -) : WidgetConfigurator { + private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>, + @Main private val mainExecutor: Executor, +) : WidgetConfigurator, OutcomeReceiver<IntentSender?, Throwable> { @AssistedFactory fun interface Factory { fun create(activity: ComponentActivity): WidgetConfigurationController } + private val activityOptions: ActivityOptions + get() = + ActivityOptions.makeBasic().apply { + pendingIntentBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + splashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR + } + private var result: CompletableDeferred<Boolean>? by nullableAtomicReference() override suspend fun configureWidget(appWidgetId: Int): Boolean = @@ -57,37 +70,64 @@ constructor( throw IllegalStateException("There is already a pending configuration") } result = CompletableDeferred() - val options = - ActivityOptions.makeBasic().apply { - pendingIntentBackgroundActivityStartMode = - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED - splashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR - } try { - // TODO(b/375036327): Add support for widget configuration if ( !glanceableHubMultiUserHelper.glanceableHubHsumFlagEnabled || !glanceableHubMultiUserHelper.isInHeadlessSystemUser() ) { + // Start configuration activity directly if we're running in a foreground user with(appWidgetHostLazy.get()) { startAppWidgetConfigureActivityForResult( activity, appWidgetId, 0, REQUEST_CODE, - options.toBundle(), + activityOptions.toBundle(), + ) + } + } else { + with(glanceableHubWidgetManagerLazy.get()) { + // Use service to get intent sender and start configuration activity + // locally if running in a headless system user + getIntentSenderForConfigureActivity( + appWidgetId, + outcomeReceiver = this@WidgetConfigurationController, + mainExecutor, ) } } - } catch (e: ActivityNotFoundException) { + } catch (_: Exception) { setConfigurationResult(Activity.RESULT_CANCELED) } - val value = result?.await() ?: false + val value = result?.await() == true result = null return@withContext value } + // Called when an intent sender is returned, and the configuration activity should be started. + override fun onResult(intentSender: IntentSender?) { + if (intentSender == null) { + setConfigurationResult(Activity.RESULT_CANCELED) + return + } + + activity.startIntentSenderForResult( + intentSender, + REQUEST_CODE, + /* fillInIntent = */ null, + /* flagsMask = */ 0, + /* flagsValues = */ 0, + /* extraFlags = */ 0, + activityOptions.toBundle(), + ) + } + + // Called when there is an error getting the intent sender. + override fun onError(e: Throwable) { + setConfigurationResult(Activity.RESULT_CANCELED) + } + fun setConfigurationResult(resultCode: Int) { result?.complete(resultCode == Activity.RESULT_OK) } 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/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index cb649f28f95b..9138243c642c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -49,6 +49,7 @@ import com.android.systemui.classifier.FalsingModule; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule; import com.android.systemui.common.data.CommonDataLayerModule; import com.android.systemui.common.ui.ConfigurationStateModule; +import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule; import com.android.systemui.common.usagestats.data.CommonUsageStatsDataLayerModule; import com.android.systemui.communal.dagger.CommunalModule; import com.android.systemui.complication.dagger.ComplicationComponent; @@ -212,6 +213,7 @@ import javax.inject.Named; CommunalModule.class, CommonDataLayerModule.class, ConfigurationStateModule.class, + ConfigurationRepositoryModule.class, CommonUsageStatsDataLayerModule.class, ConfigurationControllerModule.class, ConnectivityModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt index 2bcfea8c1179..45a59015c1b7 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt @@ -16,20 +16,16 @@ package com.android.systemui.dreams.homecontrols.service -import android.content.ComponentName -import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dreams.homecontrols.shared.IHomeControlsRemoteProxy -import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener +import com.android.systemui.dreams.homecontrols.shared.controlsSettings import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo import com.android.systemui.dump.DumpManager import com.android.systemui.util.kotlin.FlowDumperImpl -import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged @@ -45,30 +41,8 @@ constructor( @Assisted private val proxy: IHomeControlsRemoteProxy, ) : FlowDumperImpl(dumpManager) { - private companion object { - const val TAG = "HomeControlsRemoteProxy" - } - val componentInfo: Flow<HomeControlsComponentInfo> = - conflatedCallbackFlow { - val listener = - object : IOnControlsSettingsChangeListener.Stub() { - override fun onControlsSettingsChanged( - panelComponent: ComponentName?, - allowTrivialControlsOnLockscreen: Boolean, - ) { - trySendWithFailureLogging( - HomeControlsComponentInfo( - panelComponent, - allowTrivialControlsOnLockscreen, - ), - TAG, - ) - } - } - proxy.registerListenerForCurrentUser(listener) - awaitClose { proxy.unregisterListenerForCurrentUser(listener) } - } + proxy.controlsSettings .distinctUntilChanged() .stateIn(bgScope, SharingStarted.WhileSubscribed(), null) .dumpValue("componentInfo") diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt new file mode 100644 index 000000000000..2993ab5a464e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt @@ -0,0 +1,40 @@ +/* + * 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.dreams.homecontrols.shared + +import android.content.ComponentName +import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow + +val IHomeControlsRemoteProxy.controlsSettings: Flow<HomeControlsComponentInfo> + get() = conflatedCallbackFlow { + val listener = + object : IOnControlsSettingsChangeListener.Stub() { + override fun onControlsSettingsChanged( + panelComponent: ComponentName?, + allowTrivialControlsOnLockscreen: Boolean, + ) { + trySend( + HomeControlsComponentInfo(panelComponent, allowTrivialControlsOnLockscreen) + ) + } + } + registerListenerForCurrentUser(listener) + awaitClose { unregisterListenerForCurrentUser(listener) } + } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt index a65d216aa5a2..6d1fd4df2852 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt @@ -57,6 +57,11 @@ constructor(binderFactory: HomeControlsRemoteServiceBinder.Factory) : LifecycleS super.onBind(intent) return binder } + + override fun onDestroy() { + super.onDestroy() + binder.onDestroy() + } } class HomeControlsRemoteServiceBinder @@ -148,6 +153,14 @@ constructor( } } + fun onDestroy() { + logger.d("Service destroyed") + callbacks.kill() + callbackCount.set(0) + collectionJob?.cancel() + collectionJob = null + } + @AssistedFactory interface Factory { fun create(lifecycleOwner: LifecycleOwner): HomeControlsRemoteServiceBinder diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt index 4a8c040e33c1..fd913893b3ac 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt @@ -20,7 +20,6 @@ import android.app.smartspace.SmartspaceConfig import android.app.smartspace.SmartspaceManager import android.app.smartspace.SmartspaceSession import android.app.smartspace.SmartspaceTarget -import android.content.Context import android.graphics.Color import android.util.Log import android.view.View @@ -31,6 +30,7 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM +import com.android.systemui.settings.UserTracker import com.android.systemui.smartspace.SmartspacePrecondition import com.android.systemui.smartspace.SmartspaceTargetFilter import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN @@ -44,13 +44,12 @@ import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Named -/** - * Controller for managing the smartspace view on the dream - */ +/** Controller for managing the smartspace view on the dream */ @SysUISingleton -class DreamSmartspaceController @Inject constructor( - private val context: Context, - private val smartspaceManager: SmartspaceManager?, +class DreamSmartspaceController +@Inject +constructor( + private val userTracker: UserTracker, private val execution: Execution, @Main private val uiExecutor: Executor, private val smartspaceViewComponentFactory: SmartspaceViewComponent.Factory, @@ -65,6 +64,7 @@ class DreamSmartspaceController @Inject constructor( private const val TAG = "DreamSmartspaceCtrlr" } + private var userSmartspaceManager: SmartspaceManager? = null private var session: SmartspaceSession? = null private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null) private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null) @@ -78,66 +78,68 @@ class DreamSmartspaceController @Inject constructor( // Smartspace can be used on multiple displays, such as when the user casts their screen private var smartspaceViews = mutableSetOf<SmartspaceView>() - var preconditionListener = object : SmartspacePrecondition.Listener { - override fun onCriteriaChanged() { - reloadSmartspace() + var preconditionListener = + object : SmartspacePrecondition.Listener { + override fun onCriteriaChanged() { + reloadSmartspace() + } } - } init { precondition.addListener(preconditionListener) } - var filterListener = object : SmartspaceTargetFilter.Listener { - override fun onCriteriaChanged() { - reloadSmartspace() + var filterListener = + object : SmartspaceTargetFilter.Listener { + override fun onCriteriaChanged() { + reloadSmartspace() + } } - } init { targetFilter?.addListener(filterListener) } - var stateChangeListener = object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View) { - val view = v as SmartspaceView - // Until there is dream color matching - view.setPrimaryTextColor(Color.WHITE) - smartspaceViews.add(view) - connectSession() - view.setDozeAmount(0f) - } + var stateChangeListener = + object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + val view = v as SmartspaceView + // Until there is dream color matching + view.setPrimaryTextColor(Color.WHITE) + smartspaceViews.add(view) + connectSession() + view.setDozeAmount(0f) + } - override fun onViewDetachedFromWindow(v: View) { - smartspaceViews.remove(v as SmartspaceView) + override fun onViewDetachedFromWindow(v: View) { + smartspaceViews.remove(v as SmartspaceView) - if (smartspaceViews.isEmpty()) { - disconnect() + if (smartspaceViews.isEmpty()) { + disconnect() + } } } - } - private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets -> - execution.assertIsMainThread() + private val sessionListener = + SmartspaceSession.OnTargetsAvailableListener { targets -> + execution.assertIsMainThread() - // The weather data plugin takes unfiltered targets and performs the filtering internally. - weatherPlugin?.onTargetsAvailable(targets) + // The weather data plugin takes unfiltered targets and performs the filtering + // internally. + weatherPlugin?.onTargetsAvailable(targets) - onTargetsAvailableUnfiltered(targets) - val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true } - plugin?.onTargetsAvailable(filteredTargets) - } + onTargetsAvailableUnfiltered(targets) + val filteredTargets = + targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true } + plugin?.onTargetsAvailable(filteredTargets) + } - /** - * Constructs the weather view with custom layout and connects it to the weather plugin. - */ + /** Constructs the weather view with custom layout and connects it to the weather plugin. */ fun buildAndConnectWeatherView(parent: ViewGroup, customView: View?): View? { return buildAndConnectViewWithPlugin(parent, weatherPlugin, customView) } - /** - * Constructs the smartspace view and connects it to the smartspace service. - */ + /** Constructs the smartspace view and connects it to the smartspace service. */ fun buildAndConnectView(parent: ViewGroup): View? { return buildAndConnectViewWithPlugin(parent, plugin, null) } @@ -145,7 +147,7 @@ class DreamSmartspaceController @Inject constructor( private fun buildAndConnectViewWithPlugin( parent: ViewGroup, smartspaceDataPlugin: BcSmartspaceDataPlugin?, - customView: View? + customView: View?, ): View? { execution.assertIsMainThread() @@ -163,12 +165,13 @@ class DreamSmartspaceController @Inject constructor( private fun buildView( parent: ViewGroup, smartspaceDataPlugin: BcSmartspaceDataPlugin?, - customView: View? + customView: View?, ): View? { return if (smartspaceDataPlugin != null) { - val view = smartspaceViewComponentFactory.create(parent, smartspaceDataPlugin, - stateChangeListener, customView) - .getView() + val view = + smartspaceViewComponentFactory + .create(parent, smartspaceDataPlugin, stateChangeListener, customView) + .getView() if (view !is View) { return null } @@ -179,12 +182,17 @@ class DreamSmartspaceController @Inject constructor( } private fun hasActiveSessionListeners(): Boolean { - return smartspaceViews.isNotEmpty() || listeners.isNotEmpty() || + return smartspaceViews.isNotEmpty() || + listeners.isNotEmpty() || unfilteredListeners.isNotEmpty() } private fun connectSession() { - if (smartspaceManager == null) { + if (userSmartspaceManager == null) { + userSmartspaceManager = + userTracker.userContext.getSystemService(SmartspaceManager::class.java) + } + if (userSmartspaceManager == null) { return } if (plugin == null && weatherPlugin == null) { @@ -198,25 +206,21 @@ class DreamSmartspaceController @Inject constructor( return } - val newSession = smartspaceManager.createSmartspaceSession( - SmartspaceConfig.Builder(context, UI_SURFACE_DREAM).build() - ) + val newSession = + userSmartspaceManager?.createSmartspaceSession( + SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_DREAM).build() + ) Log.d(TAG, "Starting smartspace session for dream") - newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener) + newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener) this.session = newSession weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) } - plugin?.registerSmartspaceEventNotifier { - e -> - session?.notifySmartspaceEvent(e) - } + plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) } reloadSmartspace() } - /** - * Disconnects the smartspace view from the smartspace service and cleans up any resources. - */ + /** Disconnects the smartspace view from the smartspace service and cleans up any resources. */ private fun disconnect() { if (hasActiveSessionListeners()) return @@ -259,7 +263,7 @@ class DreamSmartspaceController @Inject constructor( private fun addAndRegisterListener( listener: SmartspaceTargetListener, - smartspaceDataPlugin: BcSmartspaceDataPlugin? + smartspaceDataPlugin: BcSmartspaceDataPlugin?, ) { execution.assertIsMainThread() smartspaceDataPlugin?.registerListener(listener) @@ -270,7 +274,7 @@ class DreamSmartspaceController @Inject constructor( private fun removeAndUnregisterListener( listener: SmartspaceTargetListener, - smartspaceDataPlugin: BcSmartspaceDataPlugin? + smartspaceDataPlugin: BcSmartspaceDataPlugin?, ) { execution.assertIsMainThread() smartspaceDataPlugin?.unregisterListener(listener) diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt index 349236551ecf..b2fcc434630c 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt @@ -45,7 +45,7 @@ open class DumpManager @Inject constructor() { /** See [registerCriticalDumpable]. */ fun registerCriticalDumpable(module: Dumpable) { - registerCriticalDumpable(module::class.java.canonicalName, module) + registerCriticalDumpable(module::class.java.name, module) } /** @@ -62,7 +62,7 @@ open class DumpManager @Inject constructor() { /** See [registerNormalDumpable]. */ fun registerNormalDumpable(module: Dumpable) { - registerNormalDumpable(module::class.java.canonicalName, module) + registerNormalDumpable(module::class.java.name, module) } /** @@ -104,13 +104,10 @@ open class DumpManager @Inject constructor() { dumpables[name] = DumpableEntry(module, name, priority) } - /** - * Same as the above override, but automatically uses the canonical class name as the dumpable - * name. - */ + /** Same as the above override, but automatically uses the class name as the dumpable name. */ @Synchronized fun registerDumpable(module: Dumpable) { - registerDumpable(module::class.java.canonicalName, module) + registerDumpable(module::class.java.name, module) } /** Unregisters a previously-registered dumpable. */ 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/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt index 89cdd25181cb..585f7edeb0f2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt @@ -33,13 +33,13 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository +import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import javax.inject.Inject interface StickyKeysRepository { val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> @@ -71,7 +71,7 @@ constructor( override val settingEnabled: Flow<Boolean> = secureSettingsRepository - .boolSettingForActiveUser(SETTING_KEY, defaultValue = false) + .boolSetting(SETTING_KEY, defaultValue = false) .onEach { stickyKeysLogger.logNewSettingValue(it) } .flowOn(backgroundDispatcher) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt index 032af94e62aa..2914cb9fdfdc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt @@ -44,7 +44,7 @@ constructor( private val keyguardStateController: KeyguardStateController, private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier, private val keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor, - private val keyguardTransitions: KeyguardTransitions + private val keyguardTransitions: KeyguardTransitions, ) { /** @@ -108,27 +108,28 @@ constructor( * Manager to effect the change. */ fun setSurfaceBehindVisibility(visible: Boolean) { - if (isKeyguardGoingAway == visible) { - Log.d(TAG, "WmLockscreenVisibilityManager#setVisibility -> already visible=$visible") + if (isKeyguardGoingAway && visible) { + Log.d(TAG, "#setSurfaceBehindVisibility: already visible, ignoring") return } // The surface behind is always visible if the lockscreen is not showing, so we're already // visible. if (visible && isLockscreenShowing != true) { - Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing") + Log.d(TAG, "#setSurfaceBehindVisibility: ignoring since the lockscreen isn't showing") return } - - if (visible) { if (enableNewKeyguardShellTransitions) { - keyguardTransitions.startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */) + keyguardTransitions.startKeyguardTransition( + false /* keyguardShowing */, + false, /* aodShowing */ + ) isKeyguardGoingAway = true return } - // Make the surface visible behind the keyguard by calling keyguardGoingAway. The + // Make the surface behind the keyguard visible by calling keyguardGoingAway. The // lockscreen is still showing as well, allowing us to animate unlocked. Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()") activityTaskManagerService.keyguardGoingAway(0) @@ -153,7 +154,7 @@ constructor( apps: Array<RemoteAnimationTarget>, wallpapers: Array<RemoteAnimationTarget>, nonApps: Array<RemoteAnimationTarget>, - finishedCallback: IRemoteAnimationFinishedCallback + finishedCallback: IRemoteAnimationFinishedCallback, ) { // Ensure that we've started a dismiss keyguard transition. WindowManager can start the // going away animation on its own, if an activity launches and then requests dismissing the @@ -203,27 +204,25 @@ constructor( */ private fun setWmLockscreenState( lockscreenShowing: Boolean? = this.isLockscreenShowing, - aodVisible: Boolean = this.isAodVisible + aodVisible: Boolean = this.isAodVisible, ) { - Log.d( - TAG, - "#setWmLockscreenState(" + - "isLockscreenShowing=$lockscreenShowing, " + - "aodVisible=$aodVisible)." - ) - if (lockscreenShowing == null) { Log.d( TAG, "isAodVisible=$aodVisible, but lockscreenShowing=null. Waiting for" + "non-null lockscreenShowing before calling ATMS#setLockScreenShown, which" + - "will happen once KeyguardTransitionBootInteractor starts the boot transition." + "will happen once KeyguardTransitionBootInteractor starts the boot transition.", ) this.isAodVisible = aodVisible return } if (this.isLockscreenShowing == lockscreenShowing && this.isAodVisible == aodVisible) { + Log.d( + TAG, + "#setWmLockscreenState: lockscreenShowing=$lockscreenShowing and " + + "isAodVisible=$aodVisible were both unchanged, not forwarding to ATMS.", + ) return } @@ -231,7 +230,7 @@ constructor( TAG, "ATMS#setLockScreenShown(" + "isLockscreenShowing=$lockscreenShowing, " + - "aodVisible=$aodVisible)." + "aodVisible=$aodVisible).", ) if (enableNewKeyguardShellTransitions) { keyguardTransitions.startKeyguardTransition(lockscreenShowing, aodVisible) @@ -247,7 +246,7 @@ constructor( Log.d( TAG, "#endKeyguardGoingAwayAnimation() called when isKeyguardGoingAway=false. " + - "Short-circuiting." + "Short-circuiting.", ) return } 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/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 a1f606740cd9..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 @@ -18,7 +18,8 @@ package com.android.systemui.keyguard.domain.interactor -import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.ObservableTransitionState.Idle +import com.android.compose.animation.scene.ObservableTransitionState.Transition import com.android.systemui.Flags.transitionRaceCondition import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor @@ -30,6 +31,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.device import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor import com.android.systemui.util.kotlin.Utils.Companion.toTriple @@ -110,11 +112,8 @@ constructor( } .distinctUntilChanged() - private val isDeviceEntered: Flow<Boolean> by lazy { - deviceEntryInteractor.get().isDeviceEntered - } - - private val isDeviceNotEntered: Flow<Boolean> by lazy { isDeviceEntered.map { !it } } + private val isDeviceEntered by lazy { deviceEntryInteractor.get().isDeviceEntered } + private val isDeviceNotEntered by lazy { isDeviceEntered.map { !it } } /** * Surface visibility, which is either determined by the default visibility when not @@ -124,32 +123,17 @@ constructor( @OptIn(ExperimentalCoroutinesApi::class) val surfaceBehindVisibility: Flow<Boolean> = if (SceneContainerFlag.isEnabled) { - sceneInteractor.get().transitionState.flatMapLatestConflated { transitionState -> - when (transitionState) { - is ObservableTransitionState.Transition -> - when { - transitionState.fromContent == Scenes.Lockscreen && - transitionState.toContent == Scenes.Gone -> - sceneInteractor - .get() - .isTransitionUserInputOngoing - .flatMapLatestConflated { isUserInputOngoing -> - if (isUserInputOngoing) { - isDeviceEntered - } else { - flowOf(true) - } - } - transitionState.fromContent == Scenes.Bouncer && - transitionState.toContent == Scenes.Gone -> - transitionState.progress.map { progress -> - progress > - FromPrimaryBouncerTransitionInteractor - .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD - } - else -> isDeviceEntered + sceneInteractor.get().transitionState.flatMapLatestConflated { state -> + when { + state.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Gone) -> + isDeviceEntered + state.isTransitioning(from = Scenes.Bouncer, to = Scenes.Gone) -> + (state as Transition).progress.map { progress -> + progress > + FromPrimaryBouncerTransitionInteractor + .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD } - is ObservableTransitionState.Idle -> isDeviceEntered + else -> lockscreenVisibilityWithScenes.map { !it } } } } else { @@ -219,6 +203,123 @@ constructor( } /** + * Scenes that are part of the keyguard and are shown when the device is locked or when the + * keyguard still needs to be dismissed. + */ + private val keyguardScenes = setOf(Scenes.Lockscreen, Scenes.Bouncer, Scenes.Communal) + + /** + * Scenes that don't belong in the keyguard family and cannot show when the device is locked or + * when the keyguard still needs to be dismissed. + */ + private val nonKeyguardScenes = setOf(Scenes.Gone) + + /** + * Scenes that can show regardless of device lock or keyguard dismissal states. Other sources of + * state need to be consulted to know whether the device has been entered or not. + */ + private val keyguardAgnosticScenes = + setOf( + Scenes.Shade, + Scenes.QuickSettings, + Overlays.NotificationsShade, + Overlays.QuickSettingsShade, + ) + + private val lockscreenVisibilityWithScenes = + combine( + sceneInteractor.get().transitionState.flatMapLatestConflated { + when (it) { + is Idle -> { + when (it.currentScene) { + in keyguardScenes -> flowOf(true) + in nonKeyguardScenes -> flowOf(false) + in keyguardAgnosticScenes -> isDeviceNotEntered + else -> + throw IllegalStateException("Unknown scene: ${it.currentScene}") + } + } + is Transition -> { + when { + it.isTransitioningSets(from = keyguardScenes) -> flowOf(true) + it.isTransitioningSets(from = nonKeyguardScenes) -> flowOf(false) + it.isTransitioningSets(from = keyguardAgnosticScenes) -> + isDeviceNotEntered + else -> + throw IllegalStateException("Unknown scene: ${it.fromContent}") + } + } + } + }, + wakeToGoneInteractor.canWakeDirectlyToGone, + ::Pair, + ) + .map { (lockscreenVisibilityByTransitionState, canWakeDirectlyToGone) -> + lockscreenVisibilityByTransitionState && !canWakeDirectlyToGone + } + + private val lockscreenVisibilityLegacy = + combine( + transitionInteractor.currentKeyguardState, + wakeToGoneInteractor.canWakeDirectlyToGone, + ::Pair, + ) + .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple) + .map { (currentState, canWakeDirectlyToGone, startedWithPrev) -> + val startedFromStep = startedWithPrev.previousValue + val startedStep = startedWithPrev.newValue + val returningToGoneAfterCancellation = + startedStep.to == KeyguardState.GONE && + startedFromStep.transitionState == TransitionState.CANCELED && + startedFromStep.from == KeyguardState.GONE + + val transitionInfo = + if (transitionRaceCondition()) { + transitionRepository.currentTransitionInfo + } else { + transitionRepository.currentTransitionInfoInternal.value + } + val wakingDirectlyToGone = + deviceIsAsleepInState(transitionInfo.from) && + transitionInfo.to == KeyguardState.GONE + + if (returningToGoneAfterCancellation || wakingDirectlyToGone) { + // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition, + // which means we never want to show the lockscreen throughout the + // transition. Same for waking directly to gone, due to the lockscreen being + // disabled or because the device was woken back up before the lock timeout + // duration elapsed. + false + } else if (canWakeDirectlyToGone) { + // Never show the lockscreen if we can wake directly to GONE. This means + // that the lock timeout has not yet elapsed, or the keyguard is disabled. + // In either case, we don't show the activity lock screen until one of those + // conditions changes. + false + } else if ( + currentState == KeyguardState.DREAMING && + deviceEntryInteractor.get().isUnlocked.value + ) { + // Dreams dismiss keyguard and return to GONE if they can. + false + } else if ( + startedWithPrev.newValue.from == KeyguardState.OCCLUDED && + startedWithPrev.newValue.to == KeyguardState.GONE + ) { + // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs + // when an app uses intent flags to launch over an insecure keyguard without + // dismissing it, and then manually requests keyguard dismissal while + // OCCLUDED. This transition is not user-visible; the device unlocks in the + // background and the app remains on top, while we're now GONE. In this case + // we should simply tell WM that the lockscreen is no longer visible, and + // *not* play the going away animation or related animations. + false + } else { + currentState != KeyguardState.GONE + } + } + + /** * Whether the lockscreen is visible, from the Window Manager (WM) perspective. * * Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we @@ -227,69 +328,11 @@ constructor( */ val lockscreenVisibility: Flow<Boolean> = if (SceneContainerFlag.isEnabled) { - isDeviceNotEntered - } else { - combine( - transitionInteractor.currentKeyguardState, - wakeToGoneInteractor.canWakeDirectlyToGone, - ::Pair, - ) - .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple) - .map { (currentState, canWakeDirectlyToGone, startedWithPrev) -> - val startedFromStep = startedWithPrev.previousValue - val startedStep = startedWithPrev.newValue - val returningToGoneAfterCancellation = - startedStep.to == KeyguardState.GONE && - startedFromStep.transitionState == TransitionState.CANCELED && - startedFromStep.from == KeyguardState.GONE - - val transitionInfo = - if (transitionRaceCondition()) { - transitionRepository.currentTransitionInfo - } else { - transitionRepository.currentTransitionInfoInternal.value - } - val wakingDirectlyToGone = - deviceIsAsleepInState(transitionInfo.from) && - transitionInfo.to == KeyguardState.GONE - - if (returningToGoneAfterCancellation || wakingDirectlyToGone) { - // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition, - // which means we never want to show the lockscreen throughout the - // transition. Same for waking directly to gone, due to the lockscreen being - // disabled or because the device was woken back up before the lock timeout - // duration elapsed. - false - } else if (canWakeDirectlyToGone) { - // Never show the lockscreen if we can wake directly to GONE. This means - // that the lock timeout has not yet elapsed, or the keyguard is disabled. - // In either case, we don't show the activity lock screen until one of those - // conditions changes. - false - } else if ( - currentState == KeyguardState.DREAMING && - deviceEntryInteractor.get().isUnlocked.value - ) { - // Dreams dismiss keyguard and return to GONE if they can. - false - } else if ( - startedWithPrev.newValue.from == KeyguardState.OCCLUDED && - startedWithPrev.newValue.to == KeyguardState.GONE - ) { - // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs - // when an app uses intent flags to launch over an insecure keyguard without - // dismissing it, and then manually requests keyguard dismissal while - // OCCLUDED. This transition is not user-visible; the device unlocks in the - // background and the app remains on top, while we're now GONE. In this case - // we should simply tell WM that the lockscreen is no longer visible, and - // *not* play the going away animation or related animations. - false - } else { - currentState != KeyguardState.GONE - } - } - .distinctUntilChanged() - } + lockscreenVisibilityWithScenes + } else { + lockscreenVisibilityLegacy + } + .distinctUntilChanged() /** * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index be4bc2305922..69856151e41c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -182,9 +182,11 @@ object DeviceEntryIconViewBinder { fgIconView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { // Start with an empty state + Log.d(TAG, "Initializing device entry fgIconView") fgIconView.setImageState(StateSet.NOTHING, /* merge */ false) launch("$TAG#fpIconView.viewModel") { fgViewModel.viewModel.collect { viewModel -> + Log.d(TAG, "Updating device entry icon image state $viewModel") fgIconView.setImageState( view.getIconState(viewModel.type, viewModel.useAodVariant), /* merge */ false, 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..478372d4dc0c 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,6 +30,7 @@ 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 @@ -42,8 +43,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 @@ -164,9 +167,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/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt index 67b009e50ce0..7f3ef61d02c0 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 @@ -54,9 +54,7 @@ constructor( duration = TO_LOCKSCREEN_DURATION, edge = Edge.create(from = Scenes.Communal, to = LOCKSCREEN), ) - .setupWithoutSceneContainer( - edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN), - ) + .setupWithoutSceneContainer(edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN)) val keyguardAlpha: Flow<Float> = transitionAnimation.sharedFlow( @@ -75,7 +73,7 @@ constructor( configurationInteractor .directionalDimensionPixelSize( LayoutDirection.LTR, - R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x + R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x, ) .flatMapLatest { translatePx: Int -> transitionAnimation.sharedFlowWithState( @@ -87,7 +85,7 @@ constructor( // is cancelled. onFinish = { 0f }, onCancel = { 0f }, - name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX" + name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX", ) } @@ -95,6 +93,8 @@ constructor( val shortcutsAlpha: Flow<Float> = keyguardAlpha + val statusBarAlpha: Flow<Float> = keyguardAlpha + val notificationTranslationX: Flow<Float> = keyguardTranslationX.map { it.value }.filterNotNull() } 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 378374e72c8b..dd8ff8c4a052 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 @@ -54,9 +54,7 @@ constructor( duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION, edge = Edge.create(from = LOCKSCREEN, to = Scenes.Communal), ) - .setupWithoutSceneContainer( - edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB), - ) + .setupWithoutSceneContainer(edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB)) val keyguardAlpha: Flow<Float> = transitionAnimation.sharedFlow( @@ -74,7 +72,7 @@ constructor( configurationInteractor .directionalDimensionPixelSize( LayoutDirection.LTR, - R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x + R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x, ) .flatMapLatest { translatePx: Int -> transitionAnimation.sharedFlowWithState( @@ -86,7 +84,7 @@ constructor( onFinish = { 0f }, onCancel = { 0f }, interpolator = EMPHASIZED, - name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardTranslationX" + name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardTranslationX", ) } @@ -94,6 +92,8 @@ constructor( val shortcutsAlpha: Flow<Float> = keyguardAlpha + val statusBarAlpha: Flow<Float> = keyguardAlpha + val notificationTranslationX: Flow<Float> = keyguardTranslationX.map { it.value }.filterNotNull() } 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 new file mode 100644 index 000000000000..a33685b61237 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt @@ -0,0 +1,341 @@ +/* + * 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.media.controls.domain.pipeline + +import android.content.Context +import android.graphics.drawable.Animatable +import android.graphics.drawable.Drawable +import android.media.session.MediaController +import android.media.session.MediaSession +import android.os.Handler +import android.os.Looper +import android.util.Log +import androidx.annotation.WorkerThread +import androidx.media.utils.MediaConstants +import androidx.media3.common.Player +import androidx.media3.session.CommandButton +import androidx.media3.session.SessionCommand +import androidx.media3.session.SessionToken +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.graphics.ImageLoader +import com.android.systemui.media.controls.shared.MediaControlDrawables +import com.android.systemui.media.controls.shared.MediaLogger +import com.android.systemui.media.controls.shared.model.MediaAction +import com.android.systemui.media.controls.shared.model.MediaButton +import com.android.systemui.media.controls.util.MediaControllerFactory +import com.android.systemui.media.controls.util.SessionTokenFactory +import com.android.systemui.res.R +import com.android.systemui.util.Assert +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.suspendCancellableCoroutine + +private const val TAG = "Media3ActionFactory" + +@SysUISingleton +class Media3ActionFactory +@Inject +constructor( + @Application val context: Context, + private val imageLoader: ImageLoader, + private val controllerFactory: MediaControllerFactory, + private val tokenFactory: SessionTokenFactory, + private val logger: MediaLogger, + @Background private val looper: Looper, + @Background private val handler: Handler, + @Background private val bgScope: CoroutineScope, +) { + + /** + * Generates action button info for this media session based on the Media3 session info + * + * @param packageName Package name for the media app + * @param controller The framework [MediaController] for the session + * @return The media action buttons, or null if the session token is null + */ + suspend fun createActionsFromSession( + packageName: String, + sessionToken: MediaSession.Token, + ): MediaButton? { + // Get the Media3 controller using the legacy token + val token = tokenFactory.createTokenFromLegacy(sessionToken) + val m3controller = controllerFactory.create(token, looper) + + // 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)) + } + } + return buttons + } + + /** This method must be called on the Media3 looper! */ + @WorkerThread + private fun getMedia3Actions( + packageName: String, + m3controller: androidx.media3.session.MediaController, + token: SessionToken, + ): MediaButton? { + Assert.isNotMainThread() + + // First, get standard actions + val playOrPause = + if (m3controller.playbackState == Player.STATE_BUFFERING) { + // Spinner needs to be animating to render anything. Start it here. + val drawable = + context.getDrawable(com.android.internal.R.drawable.progress_small_material) + (drawable as Animatable).start() + MediaAction( + drawable, + null, // no action to perform when clicked + context.getString(R.string.controls_media_button_connecting), + context.getDrawable(R.drawable.ic_media_connecting_container), + // Specify a rebind id to prevent the spinner from restarting on later binds. + com.android.internal.R.drawable.progress_small_material, + ) + } else { + getStandardAction(m3controller, token, Player.COMMAND_PLAY_PAUSE) + } + + val prevButton = + getStandardAction( + m3controller, + token, + Player.COMMAND_SEEK_TO_PREVIOUS, + Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, + ) + val nextButton = + getStandardAction( + m3controller, + token, + Player.COMMAND_SEEK_TO_NEXT, + Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, + ) + + // Then, get custom actions + var customActions = + m3controller.customLayout + .asSequence() + .filter { + it.isEnabled && + it.sessionCommand?.commandCode == SessionCommand.COMMAND_CODE_CUSTOM && + m3controller.isSessionCommandAvailable(it.sessionCommand!!) + } + .map { getCustomAction(packageName, token, it) } + .iterator() + fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null + + // Finally, assign the remaining button slots: play/pause A B C D + // A = previous, else custom action (if not reserved) + // B = next, else custom action (if not reserved) + // C and D are always custom actions + val reservePrev = + m3controller.sessionExtras.getBoolean( + MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, + false, + ) + val reserveNext = + m3controller.sessionExtras.getBoolean( + MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, + false, + ) + + val prevOrCustom = + prevButton + ?: if (reservePrev) { + null + } else { + nextCustomAction() + } + + val nextOrCustom = + nextButton + ?: if (reserveNext) { + null + } else { + nextCustomAction() + } + + return MediaButton( + playOrPause = playOrPause, + nextOrCustom = nextOrCustom, + prevOrCustom = prevOrCustom, + custom0 = nextCustomAction(), + custom1 = nextCustomAction(), + reserveNext = reserveNext, + reservePrev = reservePrev, + ) + } + + /** + * Create a [MediaAction] for a given command, if supported + * + * @param controller Media3 controller for the session + * @param commands Commands to check, in priority order + * @return A [MediaAction] representing the first supported command, or null if not supported + */ + private fun getStandardAction( + controller: androidx.media3.session.MediaController, + token: SessionToken, + vararg commands: @Player.Command Int, + ): MediaAction? { + for (command in commands) { + if (!controller.isCommandAvailable(command)) { + continue + } + + return when (command) { + Player.COMMAND_PLAY_PAUSE -> { + if (!controller.isPlaying) { + MediaAction( + context.getDrawable(R.drawable.ic_media_play), + { executeAction(token, Player.COMMAND_PLAY_PAUSE) }, + context.getString(R.string.controls_media_button_play), + context.getDrawable(R.drawable.ic_media_play_container), + ) + } else { + MediaAction( + context.getDrawable(R.drawable.ic_media_pause), + { executeAction(token, Player.COMMAND_PLAY_PAUSE) }, + context.getString(R.string.controls_media_button_pause), + context.getDrawable(R.drawable.ic_media_pause_container), + ) + } + } + else -> { + MediaAction( + icon = getIconForAction(command), + action = { executeAction(token, command) }, + contentDescription = getDescriptionForAction(command), + background = null, + ) + } + } + } + return null + } + + /** Get a [MediaAction] representing a [CommandButton] */ + private fun getCustomAction( + packageName: String, + token: SessionToken, + customAction: CommandButton, + ): MediaAction { + return MediaAction( + getIconForAction(customAction, packageName), + { executeAction(token, Player.COMMAND_INVALID, customAction) }, + customAction.displayName, + null, + ) + } + + private fun getIconForAction(command: @Player.Command Int): Drawable? { + return when (command) { + Player.COMMAND_SEEK_TO_PREVIOUS -> MediaControlDrawables.getPrevIcon(context) + Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM -> MediaControlDrawables.getPrevIcon(context) + Player.COMMAND_SEEK_TO_NEXT -> MediaControlDrawables.getNextIcon(context) + Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> MediaControlDrawables.getNextIcon(context) + else -> { + Log.e(TAG, "Unknown icon for $command") + null + } + } + } + + private fun getIconForAction(customAction: CommandButton, packageName: String): Drawable? { + val size = context.resources.getDimensionPixelSize(R.dimen.min_clickable_item_size) + // TODO(b/360196209): check customAction.icon field to use platform icons + if (customAction.iconResId != 0) { + val packageContext = context.createPackageContext(packageName, 0) + val source = ImageLoader.Res(customAction.iconResId, packageContext) + return runBlocking { imageLoader.loadDrawable(source, size, size) } + } + + if (customAction.iconUri != null) { + val source = ImageLoader.Uri(customAction.iconUri!!) + return runBlocking { imageLoader.loadDrawable(source, size, size) } + } + return null + } + + private fun getDescriptionForAction(command: @Player.Command Int): String? { + return when (command) { + Player.COMMAND_SEEK_TO_PREVIOUS -> + context.getString(R.string.controls_media_button_prev) + Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM -> + context.getString(R.string.controls_media_button_prev) + Player.COMMAND_SEEK_TO_NEXT -> context.getString(R.string.controls_media_button_next) + Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> + context.getString(R.string.controls_media_button_next) + else -> { + Log.e(TAG, "Unknown content description for $command") + null + } + } + } + + private fun executeAction( + token: SessionToken, + command: Int, + customAction: CommandButton? = null, + ) { + bgScope.launch { + val controller = controllerFactory.create(token, looper) + handler.post { + 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_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") + } + } + + else -> logger.logMedia3UnsupportedCommand(command.toString()) + } + controller.release() + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt index 591a9cccdadd..a176e0c1c2a6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt @@ -84,6 +84,7 @@ constructor( private val mediaFlags: MediaFlags, private val imageLoader: ImageLoader, private val statusBarManager: StatusBarManager, + private val media3ActionFactory: Media3ActionFactory, ) { private val mediaProcessingJobs = ConcurrentHashMap<String, Job>() @@ -364,7 +365,7 @@ constructor( ) } - private fun createActionsFromState( + private suspend fun createActionsFromState( packageName: String, controller: MediaController, user: UserHandle, @@ -373,6 +374,12 @@ constructor( return null } + if (mediaFlags.areMedia3ActionsEnabled(packageName, user)) { + return media3ActionFactory.createActionsFromSession( + packageName, + controller.sessionToken, + ) + } return createActionsFromState(context, packageName, controller) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt index 2bdee67dd57a..beb4d4103b11 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt @@ -141,6 +141,7 @@ private fun areActionsEqual( new: MediaData, old: MediaData, ): Boolean { + // TODO(b/360196209): account for actions generated from media3 val oldState = MediaController(context, old.token!!).playbackState return if ( new.semanticActions == null && diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt index 88c47ba4d243..0b598c13311f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt @@ -140,6 +140,10 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) { ) } + fun logMedia3UnsupportedCommand(command: String) { + buffer.log(TAG, LogLevel.DEBUG, { str1 = command }, { "Unsupported media3 command $str1" }) + } + companion object { private const val TAG = "MediaLog" } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java deleted file mode 100644 index 6caf5c20b81c..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.media.controls.util; - -import android.annotation.NonNull; -import android.content.Context; -import android.media.session.MediaController; -import android.media.session.MediaSession; - -import javax.inject.Inject; - -/** - * Testable wrapper around {@link MediaController} constructor. - */ -public class MediaControllerFactory { - - private final Context mContext; - - @Inject - public MediaControllerFactory(Context context) { - mContext = context; - } - - /** - * Creates a new MediaController from a session's token. - * - * @param token The token for the session. This value must never be null. - */ - public MediaController create(@NonNull MediaSession.Token token) { - return new MediaController(mContext, token); - } -} 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 new file mode 100644 index 000000000000..741f52998782 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt @@ -0,0 +1,45 @@ +/* + * 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. + */ +package com.android.systemui.media.controls.util + +import android.content.Context +import android.media.session.MediaController +import android.media.session.MediaSession +import android.os.Looper +import androidx.concurrent.futures.await +import androidx.media3.session.MediaController as Media3Controller +import androidx.media3.session.SessionToken +import javax.inject.Inject + +/** Testable wrapper for media controller construction */ +open class MediaControllerFactory @Inject constructor(private val context: Context) { + /** + * Creates a new [MediaController] from the framework session token. + * + * @param token The token for the session. This value must never be null. + */ + open fun create(token: MediaSession.Token): MediaController { + return MediaController(context, token) + } + + /** Creates a new [Media3Controller] from a [SessionToken] */ + open suspend fun create(token: SessionToken, looper: Looper): Media3Controller { + return Media3Controller.Builder(context, token) + .setApplicationLooper(looper) + .buildAsync() + .await() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index d4af1b546369..ac60c47ee6ab 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -18,9 +18,10 @@ package com.android.systemui.media.controls.util import android.app.StatusBarManager import android.os.UserHandle +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags +import com.android.systemui.flags.Flags as FlagsClassic import javax.inject.Inject @SysUISingleton @@ -29,22 +30,29 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlagsClass * Check whether media control actions should be based on PlaybackState instead of notification */ fun areMediaSessionActionsEnabled(packageName: String, user: UserHandle): Boolean { - // Allow global override with flag return StatusBarManager.useMediaSessionActionsForApp(packageName, user) } + /** Check whether media control actions should be derived from Media3 controller */ + fun areMedia3ActionsEnabled(packageName: String, user: UserHandle): Boolean { + val compatFlag = StatusBarManager.useMedia3ControllerForApp(packageName, user) + val featureFlag = Flags.mediaControlsButtonMedia3() + return featureFlag && compatFlag + } + /** * If true, keep active media controls for the lifetime of the MediaSession, regardless of * whether the underlying notification was dismissed */ - fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS) + fun isRetainingPlayersEnabled() = featureFlags.isEnabled(FlagsClassic.MEDIA_RETAIN_SESSIONS) /** Check whether to get progress information for resume players */ - fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS) + fun isResumeProgressEnabled() = featureFlags.isEnabled(FlagsClassic.MEDIA_RESUME_PROGRESS) /** If true, do not automatically dismiss the recommendation card */ - fun isPersistentSsCardEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_RECOMMENDATIONS) + fun isPersistentSsCardEnabled() = + featureFlags.isEnabled(FlagsClassic.MEDIA_RETAIN_RECOMMENDATIONS) /** Check whether we allow remote media to generate resume controls */ - fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME) + fun isRemoteResumeAllowed() = featureFlags.isEnabled(FlagsClassic.MEDIA_REMOTE_RESUME) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt new file mode 100644 index 000000000000..b289fd40a3a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.controls.util + +import android.content.Context +import android.media.session.MediaSession +import androidx.concurrent.futures.await +import androidx.media3.session.SessionToken +import javax.inject.Inject + +/** Testable wrapper for [SessionToken] creation */ +open class SessionTokenFactory @Inject constructor(private val context: Context) { + /** Create a new [SessionToken] from the framework [MediaSession.Token] */ + open suspend fun createTokenFromLegacy(token: MediaSession.Token): SessionToken { + return SessionToken.createSessionToken(context, token).await() + } +} 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/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java index 96c0cac53908..40613c0edc68 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java @@ -149,6 +149,7 @@ import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.data.repository.LightBarControllerStore; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -258,8 +259,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private boolean mTransientShownFromGestureOnSystemBar; private int mNavBarMode = NAV_BAR_MODE_3BUTTON; private LightBarController mLightBarController; - private final LightBarController mMainLightBarController; - private final LightBarController.Factory mLightBarControllerFactory; + private final LightBarControllerStore mLightBarControllerStore; private AutoHideController mAutoHideController; private final AutoHideController mMainAutoHideController; private final AutoHideController.Factory mAutoHideControllerFactory; @@ -580,8 +580,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements @Background Executor bgExecutor, UiEventLogger uiEventLogger, NavBarHelper navBarHelper, - LightBarController mainLightBarController, - LightBarController.Factory lightBarControllerFactory, + LightBarControllerStore lightBarControllerStore, AutoHideController mainAutoHideController, AutoHideController.Factory autoHideControllerFactory, Optional<TelecomManager> telecomManagerOptional, @@ -628,8 +627,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mUiEventLogger = uiEventLogger; mNavBarHelper = navBarHelper; mNotificationShadeDepthController = notificationShadeDepthController; - mMainLightBarController = mainLightBarController; - mLightBarControllerFactory = lightBarControllerFactory; + mLightBarControllerStore = lightBarControllerStore; mMainAutoHideController = mainAutoHideController; mAutoHideControllerFactory = autoHideControllerFactory; mTelecomManagerOptional = telecomManagerOptional; @@ -842,8 +840,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements // Unfortunately, we still need it because status bar needs LightBarController // before notifications creation. We cannot directly use getLightBarController() // from NavigationBarFragment directly. - LightBarController lightBarController = mIsOnDefaultDisplay - ? mMainLightBarController : mLightBarControllerFactory.create(mContext); + LightBarController lightBarController = mLightBarControllerStore.forDisplay(mDisplayId); setLightBarController(lightBarController); // TODO(b/118592525): to support multi-display, we start to add something which is diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt index 1c9cb3d99480..fef5a745c1ca 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt @@ -25,6 +25,7 @@ import androidx.annotation.VisibleForTesting import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope import androidx.lifecycle.repeatOnLifecycle +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.logging.MetricsLogger import com.android.systemui.animation.Expandable import com.android.systemui.dagger.qualifiers.Background @@ -48,7 +49,6 @@ 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 com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.runBlocking class ModesTile @@ -120,8 +120,7 @@ constructor( tileState = tileMapper.map(config, model) state?.apply { this.state = tileState.activationState.legacyState - val tileStateIcon = tileState.icon() - icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID) + icon = tileState.icon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID) label = tileLabel secondaryLabel = tileState.secondaryLabel contentDescription = tileState.contentDescription diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt index 9fb1d46c4241..d67057a2f476 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt @@ -30,10 +30,8 @@ import javax.inject.Inject /** Maps [AirplaneModeTileModel] to [QSTileState]. */ class AirplaneModeMapper @Inject -constructor( - @Main private val resources: Resources, - val theme: Theme, -) : QSTileDataToStateMapper<AirplaneModeTileModel> { +constructor(@Main private val resources: Resources, val theme: Theme) : + QSTileDataToStateMapper<AirplaneModeTileModel> { override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { @@ -43,16 +41,7 @@ constructor( } else { R.drawable.qs_airplane_icon_off } - - icon = { - Icon.Loaded( - resources.getDrawable( - iconRes!!, - theme, - ), - contentDescription = null - ) - } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[2] @@ -62,9 +51,6 @@ constructor( } contentDescription = label supportedActions = - setOf( - QSTileState.UserAction.CLICK, - QSTileState.UserAction.LONG_CLICK, - ) + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt index f0889433094a..7322b8d098fd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt @@ -45,6 +45,7 @@ constructor( val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm") val formatterDateOnly: DateTimeFormatter = DateTimeFormatter.ofPattern("E MMM d") } + override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { when (data) { @@ -54,13 +55,13 @@ constructor( val alarmDateTime = LocalDateTime.ofInstant( Instant.ofEpochMilli(data.alarmClockInfo.triggerTime), - TimeZone.getDefault().toZoneId() + TimeZone.getDefault().toZoneId(), ) val nowDateTime = LocalDateTime.ofInstant( Instant.ofEpochMilli(clock.currentTimeMillis()), - TimeZone.getDefault().toZoneId() + TimeZone.getDefault().toZoneId(), ) // Edge case: If it's 8:00:30 right now and alarm is requested for next week at @@ -84,7 +85,7 @@ constructor( } } iconRes = R.drawable.ic_alarm - icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) sideViewIcon = QSTileState.SideViewIcon.Chevron contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt index bcf0935adf85..5b30e8d2c86b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt @@ -29,10 +29,8 @@ import javax.inject.Inject /** Maps [BatterySaverTileModel] to [QSTileState]. */ open class BatterySaverTileMapper @Inject -constructor( - @Main protected val resources: Resources, - private val theme: Resources.Theme, -) : QSTileDataToStateMapper<BatterySaverTileModel> { +constructor(@Main protected val resources: Resources, private val theme: Resources.Theme) : + QSTileDataToStateMapper<BatterySaverTileModel> { override fun map(config: QSTileConfig, data: BatterySaverTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { @@ -41,8 +39,7 @@ constructor( iconRes = if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on else R.drawable.qs_battery_saver_icon_off - icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) } - + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) sideViewIcon = QSTileState.SideViewIcon.None if (data.isPluggedIn) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt index cad7c65ad112..7c90b3d87958 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.colorcorrection.domain import android.content.res.Resources +import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel @@ -28,17 +29,14 @@ import javax.inject.Inject /** Maps [ColorCorrectionTileModel] to [QSTileState]. */ class ColorCorrectionTileMapper @Inject -constructor( - @Main private val resources: Resources, - private val theme: Resources.Theme, -) : QSTileDataToStateMapper<ColorCorrectionTileModel> { +constructor(@Main private val resources: Resources, private val theme: Resources.Theme) : + QSTileDataToStateMapper<ColorCorrectionTileModel> { override fun map(config: QSTileConfig, data: ColorCorrectionTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { val subtitleArray = resources.getStringArray(R.array.tile_states_color_correction) - iconRes = R.drawable.ic_qs_color_correction - + icon = Icon.Loaded(resources.getDrawable(R.drawable.ic_qs_color_correction)!!, null) if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE secondaryLabel = subtitleArray[2] diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt index 984228d80b7f..60aa4ea4759f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt @@ -35,10 +35,8 @@ import javax.inject.Inject @SysUISingleton class CustomTileMapper @Inject -constructor( - private val context: Context, - private val uriGrantsManager: IUriGrantsManager, -) : QSTileDataToStateMapper<CustomTileDataModel> { +constructor(private val context: Context, private val uriGrantsManager: IUriGrantsManager) : + QSTileDataToStateMapper<CustomTileDataModel> { override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState { val userContext = @@ -50,7 +48,7 @@ constructor( val iconResult = if (userContext != null) { - getIconProvider( + getIcon( userContext = userContext, icon = data.tile.icon, callingAppUid = data.callingAppUid, @@ -58,16 +56,16 @@ constructor( defaultIcon = data.defaultTileIcon, ) } else { - IconResult({ null }, true) + IconResult(null, true) } - return QSTileState.build(iconResult.iconProvider, data.tile.label) { + return QSTileState.build(iconResult.icon, data.tile.label) { var tileState: Int = data.tile.state if (data.hasPendingBind) { tileState = Tile.STATE_UNAVAILABLE } - icon = iconResult.iconProvider + icon = iconResult.icon activationState = if (iconResult.failedToLoad) { QSTileState.ActivationState.UNAVAILABLE @@ -102,7 +100,7 @@ constructor( } @SuppressLint("MissingPermission") // android.permission.INTERACT_ACROSS_USERS_FULL - private fun getIconProvider( + private fun getIcon( userContext: Context, icon: android.graphics.drawable.Icon?, callingAppUid: Int, @@ -123,17 +121,12 @@ constructor( null } ?: defaultIcon?.loadDrawable(userContext) return IconResult( - { - drawable?.constantState?.newDrawable()?.let { - Icon.Loaded(it, contentDescription = null) - } + drawable?.constantState?.newDrawable()?.let { + Icon.Loaded(it, contentDescription = null) }, failedToLoad, ) } - class IconResult( - val iconProvider: () -> Icon?, - val failedToLoad: Boolean, - ) + class IconResult(val icon: Icon?, val failedToLoad: Boolean) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt index d7d61241fc6c..7e557ebe4639 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt @@ -30,10 +30,8 @@ import javax.inject.Inject /** Maps [FlashlightTileModel] to [QSTileState]. */ class FlashlightMapper @Inject -constructor( - @Main private val resources: Resources, - private val theme: Theme, -) : QSTileDataToStateMapper<FlashlightTileModel> { +constructor(@Main private val resources: Resources, private val theme: Theme) : + QSTileDataToStateMapper<FlashlightTileModel> { override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { @@ -43,15 +41,8 @@ constructor( } else { R.drawable.qs_flashlight_icon_off } - val icon = - Icon.Loaded( - resources.getDrawable( - iconRes!!, - theme, - ), - contentDescription = null - ) - this.icon = { icon } + + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) contentDescription = label diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt index 6b4dda13a5e6..9d44fc6ae25e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt @@ -29,23 +29,13 @@ import javax.inject.Inject /** Maps [FontScalingTileModel] to [QSTileState]. */ class FontScalingTileMapper @Inject -constructor( - @Main private val resources: Resources, - private val theme: Resources.Theme, -) : QSTileDataToStateMapper<FontScalingTileModel> { +constructor(@Main private val resources: Resources, private val theme: Resources.Theme) : + QSTileDataToStateMapper<FontScalingTileModel> { override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { iconRes = R.drawable.ic_qs_font_scaling - val icon = - Icon.Loaded( - resources.getDrawable( - iconRes!!, - theme, - ), - contentDescription = null - ) - this.icon = { icon } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) contentDescription = label activationState = QSTileState.ActivationState.ACTIVE sideViewIcon = QSTileState.SideViewIcon.Chevron diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt index 8dd611f9911a..c3ac1f8d9a72 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt @@ -36,9 +36,7 @@ constructor(@Main private val resources: Resources, private val theme: Resources QSTileState.build(resources, theme, config.uiConfig) { label = resources.getString(R.string.quick_settings_hearing_devices_label) iconRes = R.drawable.qs_hearing_devices_icon - val loadedIcon = - Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) - icon = { loadedIcon } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) sideViewIcon = QSTileState.SideViewIcon.Chevron contentDescription = label if (data.isAnyActiveHearingDevice) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt index bb0b9b7084fa..fc945851cdad 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt @@ -61,28 +61,26 @@ constructor( when (val dataIcon = data.icon) { is InternetTileIconModel.ResourceId -> { iconRes = dataIcon.resId - icon = { + icon = Icon.Loaded( resources.getDrawable(dataIcon.resId, theme), contentDescription = null, ) - } } is InternetTileIconModel.Cellular -> { val signalDrawable = SignalDrawable(context, handler) signalDrawable.setLevel(dataIcon.level) - icon = { Icon.Loaded(signalDrawable, contentDescription = null) } + icon = Icon.Loaded(signalDrawable, contentDescription = null) } is InternetTileIconModel.Satellite -> { iconRes = dataIcon.resourceIcon.res // level is inferred from res - icon = { + icon = Icon.Loaded( resources.getDrawable(dataIcon.resourceIcon.res, theme), contentDescription = null, ) - } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt index 40aee65f41a7..3692c35472f2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt @@ -30,10 +30,8 @@ import javax.inject.Inject /** Maps [ColorInversionTileModel] to [QSTileState]. */ class ColorInversionTileMapper @Inject -constructor( - @Main private val resources: Resources, - private val theme: Theme, -) : QSTileDataToStateMapper<ColorInversionTileModel> { +constructor(@Main private val resources: Resources, private val theme: Theme) : + QSTileDataToStateMapper<ColorInversionTileModel> { override fun map(config: QSTileConfig, data: ColorInversionTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { val subtitleArray = resources.getStringArray(R.array.tile_states_inversion) @@ -47,7 +45,7 @@ constructor( secondaryLabel = subtitleArray[1] iconRes = R.drawable.qs_invert_colors_icon_off } - icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt index ff931b35567f..3fe2a7734801 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt @@ -28,21 +28,26 @@ import javax.inject.Inject class IssueRecordingMapper @Inject -constructor( - @Main private val resources: Resources, - private val theme: Theme, -) : QSTileDataToStateMapper<IssueRecordingModel> { +constructor(@Main private val resources: Resources, private val theme: Theme) : + QSTileDataToStateMapper<IssueRecordingModel> { override fun map(config: QSTileConfig, data: IssueRecordingModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { - if (data.isRecording) { - activationState = QSTileState.ActivationState.ACTIVE - secondaryLabel = resources.getString(R.string.qs_record_issue_stop) - icon = { Icon.Resource(R.drawable.qs_record_issue_icon_on, null) } - } else { - icon = { Icon.Resource(R.drawable.qs_record_issue_icon_off, null) } - activationState = QSTileState.ActivationState.INACTIVE - secondaryLabel = resources.getString(R.string.qs_record_issue_start) - } + icon = + if (data.isRecording) { + activationState = QSTileState.ActivationState.ACTIVE + secondaryLabel = resources.getString(R.string.qs_record_issue_stop) + Icon.Loaded( + resources.getDrawable(R.drawable.qs_record_issue_icon_on, theme), + null, + ) + } else { + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = resources.getString(R.string.qs_record_issue_start) + Icon.Loaded( + resources.getDrawable(R.drawable.qs_record_issue_icon_off, theme), + null, + ) + } supportedActions = setOf(QSTileState.UserAction.CLICK) contentDescription = "$label, $secondaryLabel" } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt index d58f5abcd018..08432f685ea8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt @@ -30,10 +30,8 @@ import javax.inject.Inject /** Maps [LocationTileModel] to [QSTileState]. */ class LocationTileMapper @Inject -constructor( - @Main private val resources: Resources, - private val theme: Theme, -) : QSTileDataToStateMapper<LocationTileModel> { +constructor(@Main private val resources: Resources, private val theme: Theme) : + QSTileDataToStateMapper<LocationTileModel> { override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { @@ -43,17 +41,9 @@ constructor( } else { R.drawable.qs_location_icon_off } - val icon = - Icon.Loaded( - resources.getDrawable( - iconRes!!, - theme, - ), - contentDescription = null - ) - this.icon = { icon } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) - this.label = resources.getString(R.string.quick_settings_location_label) + label = resources.getString(R.string.quick_settings_location_label) if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt index 69da3134314b..4a6431359ca2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt @@ -30,14 +30,12 @@ import javax.inject.Inject class ModesTileMapper @Inject -constructor( - @Main private val resources: Resources, - val theme: Resources.Theme, -) : QSTileDataToStateMapper<ModesTileModel> { +constructor(@Main private val resources: Resources, val theme: Resources.Theme) : + QSTileDataToStateMapper<ModesTileModel> { override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { iconRes = data.iconResId - icon = { data.icon } + icon = data.icon activationState = if (data.isActivated) { QSTileState.ActivationState.ACTIVE @@ -47,10 +45,7 @@ constructor( secondaryLabel = getModesStatus(data, resources) contentDescription = "$label. $secondaryLabel" supportedActions = - setOf( - QSTileState.UserAction.CLICK, - QSTileState.UserAction.LONG_CLICK, - ) + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) sideViewIcon = QSTileState.SideViewIcon.Chevron expandedAccessibilityClass = Button::class } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt index bcf7cc763b9e..081a03c7ae67 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt @@ -57,9 +57,8 @@ constructor( activationState = QSTileState.ActivationState.INACTIVE iconRes = R.drawable.qs_nightlight_icon_off } - val loadedIcon = - Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) - icon = { loadedIcon } + + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) secondaryLabel = getSecondaryLabel(data, resources) @@ -70,7 +69,7 @@ constructor( private fun getSecondaryLabel( data: NightDisplayTileModel, - resources: Resources + resources: Resources, ): CharSequence? { when (data) { is NightDisplayTileModel.AutoModeTwilight -> { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt index 40809960735f..8e5d0d4eb3dc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt @@ -29,17 +29,15 @@ import javax.inject.Inject /** Maps [OneHandedModeTileModel] to [QSTileState]. */ class OneHandedModeTileMapper @Inject -constructor( - @Main private val resources: Resources, - private val theme: Resources.Theme, -) : QSTileDataToStateMapper<OneHandedModeTileModel> { +constructor(@Main private val resources: Resources, private val theme: Resources.Theme) : + QSTileDataToStateMapper<OneHandedModeTileModel> { override fun map(config: QSTileConfig, data: OneHandedModeTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded) label = resources.getString(R.string.quick_settings_onehanded_label) iconRes = com.android.internal.R.drawable.ic_qs_one_handed_mode - icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE secondaryLabel = subtitleArray[2] diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt index 823174234b13..5c6351e88494 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt @@ -29,17 +29,15 @@ import javax.inject.Inject /** Maps [QRCodeScannerTileModel] to [QSTileState]. */ class QRCodeScannerTileMapper @Inject -constructor( - @Main private val resources: Resources, - private val theme: Resources.Theme, -) : QSTileDataToStateMapper<QRCodeScannerTileModel> { +constructor(@Main private val resources: Resources, private val theme: Resources.Theme) : + QSTileDataToStateMapper<QRCodeScannerTileModel> { override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { label = resources.getString(R.string.qr_code_scanner_title) contentDescription = label iconRes = R.drawable.ic_qr_code_scanner - icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) sideViewIcon = QSTileState.SideViewIcon.Chevron supportedActions = setOf(QSTileState.UserAction.CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt index 85ee02207ac6..fe77fe61b4bf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt @@ -30,10 +30,8 @@ import javax.inject.Inject /** Maps [ReduceBrightColorsTileModel] to [QSTileState]. */ class ReduceBrightColorsTileMapper @Inject -constructor( - @Main private val resources: Resources, - private val theme: Resources.Theme, -) : QSTileDataToStateMapper<ReduceBrightColorsTileModel> { +constructor(@Main private val resources: Resources, private val theme: Resources.Theme) : + QSTileDataToStateMapper<ReduceBrightColorsTileModel> { override fun map(config: QSTileConfig, data: ReduceBrightColorsTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { @@ -50,12 +48,7 @@ constructor( resources .getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_INACTIVE] } - icon = { - Icon.Loaded( - drawable = resources.getDrawable(iconRes!!, theme), - contentDescription = null - ) - } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) label = resources.getString(com.android.internal.R.string.reduce_bright_colors_feature_name) contentDescription = label diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt index 33dc6ed7a1e8..9a003ffdf7de 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt @@ -36,36 +36,33 @@ constructor( @Main private val resources: Resources, private val theme: Resources.Theme, private val devicePostureController: DevicePostureController, - private val deviceStateManager: DeviceStateManager + private val deviceStateManager: DeviceStateManager, ) : QSTileDataToStateMapper<RotationLockTileModel> { override fun map(config: QSTileConfig, data: RotationLockTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { - this.label = resources.getString(R.string.quick_settings_rotation_unlocked_label) - this.contentDescription = - resources.getString(R.string.accessibility_quick_settings_rotation) + label = resources.getString(R.string.quick_settings_rotation_unlocked_label) + contentDescription = resources.getString(R.string.accessibility_quick_settings_rotation) if (data.isRotationLocked) { activationState = QSTileState.ActivationState.INACTIVE - this.secondaryLabel = EMPTY_SECONDARY_STRING + secondaryLabel = EMPTY_SECONDARY_STRING iconRes = R.drawable.qs_auto_rotate_icon_off } else { activationState = QSTileState.ActivationState.ACTIVE - this.secondaryLabel = + secondaryLabel = if (data.isCameraRotationEnabled) { resources.getString(R.string.rotation_lock_camera_rotation_on) } else { EMPTY_SECONDARY_STRING } - this.iconRes = R.drawable.qs_auto_rotate_icon_on - } - this.icon = { - Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) + iconRes = R.drawable.qs_auto_rotate_icon_on } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) if (isDeviceFoldable(resources, deviceStateManager)) { - this.secondaryLabel = getSecondaryLabelWithPosture(this.activationState) + secondaryLabel = getSecondaryLabelWithPosture(activationState) } - this.stateDescription = this.secondaryLabel - this.sideViewIcon = QSTileState.SideViewIcon.None + stateDescription = secondaryLabel + sideViewIcon = QSTileState.SideViewIcon.None supportedActions = setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) } @@ -86,7 +83,7 @@ constructor( return resources.getString( R.string.rotation_tile_with_posture_secondary_label_template, stateName, - posture + posture, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt index 888bba87a03a..08196bbfe2f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt @@ -29,10 +29,8 @@ import javax.inject.Inject /** Maps [DataSaverTileModel] to [QSTileState]. */ class DataSaverTileMapper @Inject -constructor( - @Main private val resources: Resources, - private val theme: Resources.Theme, -) : QSTileDataToStateMapper<DataSaverTileModel> { +constructor(@Main private val resources: Resources, private val theme: Resources.Theme) : + QSTileDataToStateMapper<DataSaverTileModel> { override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { with(data) { @@ -45,9 +43,7 @@ constructor( iconRes = R.drawable.qs_data_saver_icon_off secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1] } - val loadedIcon = - Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) - icon = { loadedIcon } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt index e74e77f29007..ba06de966c10 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt @@ -30,10 +30,8 @@ import javax.inject.Inject /** Maps [ScreenRecordModel] to [QSTileState]. */ class ScreenRecordTileMapper @Inject -constructor( - @Main private val resources: Resources, - private val theme: Resources.Theme, -) : QSTileDataToStateMapper<ScreenRecordModel> { +constructor(@Main private val resources: Resources, private val theme: Resources.Theme) : + QSTileDataToStateMapper<ScreenRecordModel> { override fun map(config: QSTileConfig, data: ScreenRecordModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { label = resources.getString(R.string.quick_settings_screen_record_label) @@ -43,24 +41,12 @@ constructor( is ScreenRecordModel.Recording -> { activationState = QSTileState.ActivationState.ACTIVE iconRes = R.drawable.qs_screen_record_icon_on - val loadedIcon = - Icon.Loaded( - resources.getDrawable(iconRes!!, theme), - contentDescription = null - ) - icon = { loadedIcon } sideViewIcon = QSTileState.SideViewIcon.None secondaryLabel = resources.getString(R.string.quick_settings_screen_record_stop) } is ScreenRecordModel.Starting -> { activationState = QSTileState.ActivationState.ACTIVE iconRes = R.drawable.qs_screen_record_icon_on - val loadedIcon = - Icon.Loaded( - resources.getDrawable(iconRes!!, theme), - contentDescription = null - ) - icon = { loadedIcon } val countDown = data.countdownSeconds sideViewIcon = QSTileState.SideViewIcon.None secondaryLabel = String.format("%d...", countDown) @@ -68,17 +54,13 @@ constructor( is ScreenRecordModel.DoingNothing -> { activationState = QSTileState.ActivationState.INACTIVE iconRes = R.drawable.qs_screen_record_icon_off - val loadedIcon = - Icon.Loaded( - resources.getDrawable(iconRes!!, theme), - contentDescription = null - ) - icon = { loadedIcon } sideViewIcon = QSTileState.SideViewIcon.Chevron // tapping will open dialog secondaryLabel = resources.getString(R.string.quick_settings_screen_record_start) } } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) + contentDescription = if (TextUtils.isEmpty(secondaryLabel)) label else TextUtils.concat(label, ", ", secondaryLabel) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt index 597cf274dcff..b4cfec48fb0a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt @@ -51,8 +51,7 @@ constructor( supportedActions = setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) iconRes = sensorPrivacyTileResources.getIconRes(data.isBlocked) - icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) } - + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) sideViewIcon = QSTileState.SideViewIcon.None if (data.isBlocked) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt index f29c745d8119..eda8e5ce8c43 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt @@ -34,14 +34,13 @@ import javax.inject.Inject /** Maps [UiModeNightTileModel] to [QSTileState]. */ class UiModeNightTileMapper @Inject -constructor( - @Main private val resources: Resources, - private val theme: Theme, -) : QSTileDataToStateMapper<UiModeNightTileModel> { +constructor(@Main private val resources: Resources, private val theme: Theme) : + QSTileDataToStateMapper<UiModeNightTileModel> { companion object { val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a") val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm") } + override fun map(config: QSTileConfig, data: UiModeNightTileModel): QSTileState = with(data) { QSTileState.build(resources, theme, config.uiConfig) { @@ -76,7 +75,7 @@ constructor( if (isNightMode) R.string.quick_settings_dark_mode_secondary_label_until else R.string.quick_settings_dark_mode_secondary_label_on_at, - formatter.format(time) + formatter.format(time), ) } else if ( nightModeCustomType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME @@ -121,9 +120,7 @@ constructor( if (activationState == QSTileState.ActivationState.ACTIVE) R.drawable.qs_light_dark_theme_icon_on else R.drawable.qs_light_dark_theme_icon_off - val loadedIcon = - Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) - icon = { loadedIcon } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) supportedActions = if (activationState == QSTileState.ActivationState.UNAVAILABLE) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt index eee95b7311d3..a1bc8a889a1b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt @@ -42,9 +42,7 @@ constructor( label = getTileLabel()!! contentDescription = label iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status - icon = { - Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) - } + icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) when (data) { is WorkModeTileModel.HasActiveProfile -> { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt index 549f0a73908d..8394be5e0a38 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt @@ -35,7 +35,7 @@ import kotlin.reflect.KClass * // TODO(b/http://b/299909989): Clean up legacy mappings after the transition */ data class QSTileState( - val icon: () -> Icon?, + val icon: Icon?, val iconRes: Int?, val label: CharSequence, val activationState: ActivationState, @@ -54,21 +54,18 @@ data class QSTileState( resources: Resources, theme: Theme, config: QSTileUIConfig, - builder: Builder.() -> Unit + builder: Builder.() -> Unit, ): QSTileState { val iconDrawable = resources.getDrawable(config.iconRes, theme) return build( - { Icon.Loaded(iconDrawable, null) }, + Icon.Loaded(iconDrawable, null), resources.getString(config.labelRes), builder, ) } - fun build( - icon: () -> Icon?, - label: CharSequence, - builder: Builder.() -> Unit - ): QSTileState = Builder(icon, label).apply { builder() }.build() + fun build(icon: Icon?, label: CharSequence, builder: Builder.() -> Unit): QSTileState = + Builder(icon, label).apply { builder() }.build() } enum class ActivationState(val legacyState: Int) { @@ -117,10 +114,7 @@ data class QSTileState( data object None : SideViewIcon } - class Builder( - var icon: () -> Icon?, - var label: CharSequence, - ) { + class Builder(var icon: Icon?, var label: CharSequence) { var iconRes: Int? = null var activationState: ActivationState = ActivationState.INACTIVE var secondaryLabel: CharSequence? = null diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index f89745f49cc8..35b1b9636263 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles.viewmodel import android.content.Context import android.os.UserHandle import android.util.Log +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.logging.InstanceId import com.android.systemui.Dumpable import com.android.systemui.animation.Expandable @@ -42,7 +43,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.takeWhile -import com.android.app.tracing.coroutines.launchTraced as launch // TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout class QSTileViewModelAdapter @@ -223,7 +223,7 @@ constructor( fun mapState( context: Context, viewModelState: QSTileState, - config: QSTileConfig + config: QSTileConfig, ): QSTile.State = // we have to use QSTile.BooleanState to support different side icons // which are bound to instanceof QSTile.BooleanState in QSTileView. @@ -241,7 +241,7 @@ constructor( viewModelState.supportedActions.contains(QSTileState.UserAction.TOGGLE_CLICK) icon = - when (val stateIcon = viewModelState.icon()) { + when (val stateIcon = viewModelState.icon) { is Icon.Loaded -> if (viewModelState.iconRes == null) DrawableIcon(stateIcon.drawable) else DrawableIconWithRes(stateIcon.drawable, viewModelState.iconRes) diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt index 6d5bf328d00b..e922e09c26ec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt @@ -22,6 +22,7 @@ import android.os.Bundle import android.view.View import androidx.annotation.VisibleForTesting import androidx.asynclayoutinflater.view.AsyncLayoutInflater +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.settingslib.applications.InterestingConfigChanges import com.android.systemui.Dumpable import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor @@ -57,7 +58,6 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext // TODO(307945185) Split View concerns into a ViewBinder 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 f3c6190d2f7b..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, @@ -213,7 +210,6 @@ constructor( /** Updates the visibility of the scene container. */ private fun hydrateVisibility() { applicationScope.launch { - // TODO(b/296114544): Combine with some global hun state to make it visible! deviceProvisioningInteractor.isDeviceProvisioned .flatMapLatest { isAllowedToBeVisible -> if (isAllowedToBeVisible) { @@ -271,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/screenshot/data/model/DisplayContentModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt index 2048b7c0c142..137b4fdf89bc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.screenshot.data.model import android.app.ActivityTaskManager.RootTaskInfo +import com.android.systemui.screenshot.policy.childTasksTopDown /** Information about the tasks on a display. */ data class DisplayContentModel( @@ -27,3 +28,5 @@ data class DisplayContentModel( /** A list of root tasks on the display, ordered from top to bottom along the z-axis */ val rootTasks: List<RootTaskInfo>, ) + +fun DisplayContentModel.allTasks() = rootTasks.asSequence().flatMap { it.childTasksTopDown() } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt index 5e2b57651de7..2a4fe3ebfb92 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt @@ -16,15 +16,17 @@ package com.android.systemui.screenshot.policy -import android.content.ComponentName import android.os.UserHandle -/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */ data class CaptureParameters( - /** How should the content be captured? */ + /** Describes how the image should be obtained. */ val type: CaptureType, - /** The focused or top component at the time of the screenshot. */ - val component: ComponentName?, - /** Which user should receive the screenshot file? */ + /** Which user to receive the image. */ val owner: UserHandle, + /** + * The task which represents the main content or focal point of the screenshot. This is the task + * used for retrieval of [AssistContent][android.app.assist.AssistContent] as well as + * [Scroll Capture][android.view.IWindowManager.requestScrollCapture]. + */ + val contentTask: TaskReference, ) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt index 0fb536636f1c..73ff566b7306 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt @@ -22,7 +22,7 @@ import com.android.systemui.screenshot.data.model.DisplayContentModel fun interface CapturePolicy { /** * Test the policy against the current display task state. If the policy applies, Returns a - * [PolicyResult.Matched] containing [CaptureParameters] used to alter the request. + * [PolicyResult.Matched] containing [LegacyCaptureParameters] used to alter the request. */ suspend fun check(content: DisplayContentModel): PolicyResult @@ -35,7 +35,7 @@ fun interface CapturePolicy { /** Why the policy matched. */ val reason: String, /** Details on how to modify the screen capture request. */ - val parameters: CaptureParameters, + val parameters: LegacyCaptureParameters, ) : PolicyResult /** The policy rules do not match the given display content and do not apply. */ @@ -43,7 +43,7 @@ fun interface CapturePolicy { /** The name of the policy rule which matched. */ val policy: String, /** Why the policy did not match. */ - val reason: String + val reason: String, ) : PolicyResult } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt index 945520126474..34c851100d7f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt @@ -20,12 +20,10 @@ import android.graphics.Rect /** What to capture */ sealed interface CaptureType { + /** Capture the entire screen contents. */ data class FullScreen(val displayId: Int) : CaptureType /** Capture the contents of the task only. */ data class IsolatedTask(val taskId: Int, val taskBounds: Rect?) : CaptureType - - data class RootTask(val parentTaskId: Int, val taskBounds: Rect?, val childTaskIds: List<Int>) : - CaptureType } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt new file mode 100644 index 000000000000..4b697b201cee --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt @@ -0,0 +1,30 @@ +/* + * 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.screenshot.policy + +import android.content.ComponentName +import android.os.UserHandle + +/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */ +data class LegacyCaptureParameters( + /** How should the content be captured? */ + val type: CaptureType, + /** The focused or top component at the time of the screenshot. */ + val component: ComponentName?, + /** Which user should receive the screenshot file? */ + val owner: UserHandle, +) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt index e840668688a0..a84cdf40ca69 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt @@ -80,6 +80,7 @@ class PolicyRequestProcessor( Log.i(TAG, "$result") return modify(original, result.parameters) } + is NotMatched -> Log.i(TAG, "$result") } } @@ -89,7 +90,45 @@ class PolicyRequestProcessor( } /** Produce a new [ScreenshotData] using [CaptureParameters] */ - suspend fun modify(original: ScreenshotData, updates: CaptureParameters): ScreenshotData { + suspend fun modify(original: ScreenshotData, params: CaptureParameters): ScreenshotData { + Log.d(TAG, "[modify] CaptureParameters = $params") + // Update and apply bitmap capture depending on the parameters. + when (val type = params.type) { + is IsolatedTask -> { + Log.i(TAG, "Capturing task snapshot: $params") + + val taskSnapshot = + capture.captureTask(type.taskId) ?: error("Failed to capture task") + + return original.copy( + type = TAKE_SCREENSHOT_PROVIDED_IMAGE, + bitmap = taskSnapshot, + userHandle = params.owner, + taskId = params.contentTask.taskId, + topComponent = params.contentTask.component, + originalScreenBounds = type.taskBounds, + ) + } + + is FullScreen -> { + Log.i(TAG, "Capturing screenshot: $params") + + val screenshot = + captureDisplay(type.displayId) ?: error("Failed to capture screenshot") + return original.copy( + type = TAKE_SCREENSHOT_FULLSCREEN, + bitmap = screenshot, + userHandle = params.owner, + topComponent = params.contentTask.component, + originalScreenBounds = Rect(0, 0, screenshot.width, screenshot.height), + taskId = params.contentTask.taskId, + ) + } + } + } + + /** Produce a new [ScreenshotData] using [LegacyCaptureParameters] */ + suspend fun modify(original: ScreenshotData, updates: LegacyCaptureParameters): ScreenshotData { Log.d(TAG, "[modify] CaptureParameters = $updates") // Update and apply bitmap capture depending on the parameters. val updated = @@ -102,14 +141,7 @@ class PolicyRequestProcessor( type.taskId, type.taskBounds, ) - is CaptureType.RootTask -> - replaceWithTaskSnapshot( - original, - updates.component, - updates.owner, - type.parentTaskId, - type.taskBounds, - ) + is FullScreen -> replaceWithScreenshot( original, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt index 1945c2575655..3857ba305ad0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt @@ -31,11 +31,8 @@ import javax.inject.Inject * * Parameters: Capture the whole screen, owned by the private user. */ -class PrivateProfilePolicy -@Inject -constructor( - private val profileTypes: ProfileTypeRepository, -) : CapturePolicy { +class PrivateProfilePolicy @Inject constructor(private val profileTypes: ProfileTypeRepository) : + CapturePolicy { override suspend fun check(content: DisplayContentModel): PolicyResult { // The systemUI notification shade isn't a private profile app, skip. if (content.systemUiState.shadeExpanded) { @@ -47,25 +44,23 @@ constructor( content.rootTasks .filter { it.isVisible } .firstNotNullOfOrNull { root -> - root - .childTasksTopDown() - .firstOrNull { - profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE - } - } - ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS) + root.childTasksTopDown().firstOrNull { + profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE + } + } ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS) // If matched, return parameters needed to modify the request. return Matched( policy = NAME, reason = PRIVATE_TASK_VISIBLE, - CaptureParameters( + LegacyCaptureParameters( type = FullScreen(content.displayId), component = content.rootTasks.first { it.isVisible }.topActivity, owner = UserHandle.of(childTask.userId), - ) + ), ) } + companion object { const val NAME = "PrivateProfile" const val SHADE_EXPANDED = "Notification shade is expanded" diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt index dd39f92643ce..c43e929295e1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt @@ -20,7 +20,7 @@ import android.app.ActivityTaskManager.RootTaskInfo import com.android.systemui.screenshot.data.model.ChildTaskModel /** The child tasks of A RootTaskInfo as [ChildTaskModel] in top-down (z-index ascending) order. */ -internal fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> { +fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> { return ((childTaskIds.size - 1) downTo 0).asSequence().map { index -> ChildTaskModel( childTaskIds[index], diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt index 9967afffb6a0..5213579d5ee6 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt @@ -20,18 +20,16 @@ import android.app.ActivityTaskManager.RootTaskInfo import android.app.WindowConfiguration import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN -import android.app.WindowConfiguration.WINDOWING_MODE_PINNED import android.content.ComponentName +import android.graphics.Rect import android.os.UserHandle import android.util.Log import com.android.systemui.screenshot.data.model.DisplayContentModel -import com.android.systemui.screenshot.data.model.ProfileType import com.android.systemui.screenshot.data.model.ProfileType.PRIVATE import com.android.systemui.screenshot.data.model.ProfileType.WORK import com.android.systemui.screenshot.data.repository.ProfileTypeRepository import com.android.systemui.screenshot.policy.CaptureType.FullScreen import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask -import com.android.systemui.screenshot.policy.CaptureType.RootTask import javax.inject.Inject private const val TAG = "ScreenshotPolicy" @@ -39,7 +37,7 @@ private const val TAG = "ScreenshotPolicy" /** Determines what to capture and which user owns the output. */ class ScreenshotPolicy @Inject constructor(private val profileTypes: ProfileTypeRepository) { /** - * Apply the policy to the content, resulting in [CaptureParameters]. + * Apply the policy to the content, resulting in [LegacyCaptureParameters]. * * @param content the content of the display * @param defaultComponent the component associated with the screenshot by default @@ -53,7 +51,7 @@ class ScreenshotPolicy @Inject constructor(private val profileTypes: ProfileType val defaultFullScreen by lazy { CaptureParameters( type = FullScreen(displayId = content.displayId), - component = defaultComponent, + contentTask = TaskReference(-1, defaultComponent, defaultOwner, Rect()), owner = defaultOwner, ) } @@ -70,32 +68,47 @@ class ScreenshotPolicy @Inject constructor(private val profileTypes: ProfileType } ?: return defaultFullScreen Log.d(TAG, "topRootTask: $topRootTask") - val rootTaskOwners = topRootTask.childTaskUserIds.distinct() - // Special case: Only WORK in top root task which is full-screen or maximized freeform + // When: + // * there is one or more child task + // * all owned by the same user + // * this user is a work profile + // * the root task is fullscreen or freeform-maximized + // + // Then: + // the result will be a task snapshot instead of a full screen capture. If there is more + // than one child task, the root task will be snapshot to include any/all child tasks. This + // is intended to cover split-screen mode. + val rootTaskOwners = topRootTask.childTaskUserIds.distinct() if ( rootTaskOwners.size == 1 && profileTypes.getProfileType(rootTaskOwners.single()) == WORK && (topRootTask.isFullScreen() || topRootTask.isMaximizedFreeform()) ) { + val topChildTask = topRootTask.childTasksTopDown().first() + + // If there is more than one task, capture the parent to include both. val type = if (topRootTask.childTaskCount() > 1) { - RootTask( - parentTaskId = topRootTask.taskId, - taskBounds = topRootTask.bounds, - childTaskIds = topRootTask.childTasksTopDown().map { it.id }.toList(), - ) + IsolatedTask(taskId = topRootTask.taskId, taskBounds = topRootTask.bounds) } else { - IsolatedTask( - taskId = topRootTask.childTasksTopDown().first().id, - taskBounds = topRootTask.bounds, - ) + // Otherwise capture the single task, and use its bounds. + IsolatedTask(taskId = topChildTask.id, taskBounds = topChildTask.bounds) } - // Capture the RootTask (and all children) + + // The content task (the focus of the screenshot) must represent a single task + // containing an activity, so always reference the top child task here. The owner + // of the screenshot here is always the same as well. return CaptureParameters( type = type, - component = topRootTask.topActivity, - owner = UserHandle.of(rootTaskOwners.single()), + contentTask = + TaskReference( + taskId = topChildTask.id, + component = topRootTask.topActivity ?: defaultComponent, + owner = UserHandle.of(topChildTask.userId), + bounds = topChildTask.bounds, + ), + owner = UserHandle.of(topChildTask.userId), ) } @@ -105,26 +118,36 @@ class ScreenshotPolicy @Inject constructor(private val profileTypes: ProfileType val visibleChildTasks = content.rootTasks.filter { it.isVisible }.flatMap { it.childTasksTopDown() } + // Don't target a PIP window as the screenshot "content", it should only be used + // to determine ownership (above). + val contentTask = + content.rootTasks + .filter { + it.isVisible && it.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED + } + .flatMap { it.childTasksTopDown() } + .first() + val allVisibleProfileTypes = visibleChildTasks .map { it.userId } .distinct() .associate { profileTypes.getProfileType(it) to UserHandle.of(it) } - // If any visible content belongs to the private profile user -> private profile - // otherwise the personal user (including partial screen work content). - val ownerHandle = - allVisibleProfileTypes[PRIVATE] - ?: allVisibleProfileTypes[ProfileType.NONE] - ?: defaultOwner - - // Attribute to the component of top-most task owned by this user (or fallback to default) - val topComponent = - visibleChildTasks.firstOrNull { it.userId == ownerHandle.identifier }?.componentName + // If any task is visible and owned by a PRIVATE profile user, the screenshot is assigned + // to that user. Work profile has been handled above so it is not considered here. Fallback + // to the default user which is the primary "current" user ('aka' personal "profile"). + val ownerHandle = allVisibleProfileTypes[PRIVATE] ?: defaultOwner return CaptureParameters( type = FullScreen(content.displayId), - component = topComponent ?: topRootTask.topActivity ?: defaultComponent, + contentTask = + TaskReference( + taskId = contentTask.id, + component = contentTask.componentName, + owner = UserHandle.of(contentTask.userId), + bounds = contentTask.bounds, + ), owner = ownerHandle, ) } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt new file mode 100644 index 000000000000..04f5b1ef2885 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt @@ -0,0 +1,32 @@ +/* + * 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.screenshot.policy + +import android.content.ComponentName +import android.graphics.Rect +import android.os.UserHandle + +data class TaskReference( + /** The id of the task. */ + val taskId: Int, + /** The component name of the task. */ + val component: ComponentName?, + /** The owner of the task. */ + val owner: UserHandle, + /** The bounds of the task. */ + val bounds: Rect, +) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt index cf90c0a58e94..109c1cbf7e8e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt @@ -68,7 +68,7 @@ constructor(private val profileTypes: ProfileTypeRepository, private val context return PolicyResult.Matched( policy = NAME, reason = WORK_TASK_IS_TOP, - CaptureParameters( + LegacyCaptureParameters( type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds), component = childTask.componentName ?: rootTask.topActivity, owner = UserHandle.of(childTask.userId), diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt new file mode 100644 index 000000000000..3d7b2ea2bc6e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt @@ -0,0 +1,83 @@ +/* + * 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.settings + +import android.content.ContentResolver +import com.android.systemui.Flags +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository +import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl +import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository +import com.android.systemui.shared.settings.data.repository.SystemSettingsRepositoryImpl +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SystemSettings +import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository +import com.android.systemui.util.settings.repository.UserAwareSystemSettingsRepository +import dagger.Lazy +import dagger.Module +import dagger.Provides +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineDispatcher + +@Module +object UserSettingsRepositoryModule { + @JvmStatic + @Provides + @SysUISingleton + fun provideSecureSettingsRepository( + secureSettings: Lazy<SecureSettings>, + userRepository: Lazy<UserRepository>, + contentResolver: Lazy<ContentResolver>, + @Background backgroundDispatcher: CoroutineDispatcher, + @Background backgroundContext: CoroutineContext, + ): SecureSettingsRepository { + return if (Flags.userAwareSettingsRepositories()) { + UserAwareSecureSettingsRepository( + secureSettings.get(), + userRepository.get(), + backgroundDispatcher, + backgroundContext, + ) + } else { + SecureSettingsRepositoryImpl(contentResolver.get(), backgroundDispatcher) + } + } + + @JvmStatic + @Provides + @SysUISingleton + fun provideSystemSettingsRepository( + systemSettings: Lazy<SystemSettings>, + userRepository: Lazy<UserRepository>, + contentResolver: Lazy<ContentResolver>, + @Background backgroundDispatcher: CoroutineDispatcher, + @Background backgroundContext: CoroutineContext, + ): SystemSettingsRepository { + return if (Flags.userAwareSettingsRepositories()) { + UserAwareSystemSettingsRepository( + systemSettings.get(), + userRepository.get(), + backgroundDispatcher, + backgroundContext, + ) + } else { + SystemSettingsRepositoryImpl(contentResolver.get(), backgroundDispatcher) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt index 6e63446d88d8..1776a2c4a439 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt @@ -18,14 +18,14 @@ package com.android.systemui.shade import android.content.Context import android.view.ViewGroup -import com.android.systemui.res.R import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.statusbar.StatusBarState.SHADE -import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED +import com.android.systemui.res.R import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate +import com.android.systemui.statusbar.StatusBarState.SHADE +import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED import com.android.systemui.unfold.SysUIUnfoldScope import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import javax.inject.Inject @@ -39,8 +39,10 @@ constructor( progressProvider: NaturalRotationUnfoldProgressProvider, ) { - private val filterShade: () -> Boolean = { statusBarStateController.getState() == SHADE || - statusBarStateController.getState() == SHADE_LOCKED } + private val filterShade: () -> Boolean = { + statusBarStateController.getState() == SHADE || + statusBarStateController.getState() == SHADE_LOCKED + } private val translateAnimator by lazy { UnfoldConstantTranslateAnimator( @@ -48,21 +50,23 @@ constructor( setOf( ViewIdToTranslate(R.id.quick_settings_panel, START, filterShade), ViewIdToTranslate(R.id.qs_footer_actions, START, filterShade), - ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade)), - progressProvider = progressProvider) + ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade), + ), + progressProvider = progressProvider, + ) } private val translateAnimatorStatusBar by lazy { UnfoldConstantTranslateAnimator( viewsIdToTranslate = - setOf( - ViewIdToTranslate(R.id.shade_header_system_icons, END, filterShade), - ViewIdToTranslate(R.id.privacy_container, END, filterShade), - ViewIdToTranslate(R.id.carrier_group, END, filterShade), - ViewIdToTranslate(R.id.clock, START, filterShade), - ViewIdToTranslate(R.id.date, START, filterShade) - ), - progressProvider = progressProvider + setOf( + ViewIdToTranslate(R.id.shade_header_system_icons, END, filterShade), + ViewIdToTranslate(R.id.privacy_container, END, filterShade), + ViewIdToTranslate(R.id.carrier_group, END, filterShade), + ViewIdToTranslate(R.id.clock, START, filterShade), + ViewIdToTranslate(R.id.date, START, filterShade), + ), + progressProvider = progressProvider, ) } @@ -73,10 +77,7 @@ constructor( val splitShadeStatusBarViewGroup: ViewGroup? = root.findViewById(R.id.split_shade_status_bar) if (splitShadeStatusBarViewGroup != null) { - translateAnimatorStatusBar.init( - splitShadeStatusBarViewGroup, - translationMax - ) + translateAnimatorStatusBar.init(splitShadeStatusBarViewGroup, translationMax) } } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 0e82bf82fdf9..c15c8f946855 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2053,6 +2053,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } if (mQsController.getExpanded()) { mQsController.flingQs(0, FLING_COLLAPSE); + } else if (mBarState == KEYGUARD) { + mLockscreenShadeTransitionController.goToLockedShade( + /* expandedView= */null, /* needsQSAnimation= */false); } else { expand(true /* animate */); } @@ -3109,7 +3112,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump if (isTracking()) { onTrackingStopped(true); } - if (isExpanded() && !mQsController.getExpanded()) { + if (isExpanded() && mBarState != KEYGUARD && !mQsController.getExpanded()) { mShadeLog.d("Status Bar was long pressed. Expanding to QS."); expandToQs(); } else { @@ -5091,13 +5094,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } boolean handled = mHeadsUpTouchHelper.onTouchEvent(event); - if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch( - event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) { - if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { - mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event"); - } - return true; - } // This touch session has already resulted in shade expansion. Ignore everything else. if (ShadeExpandsOnStatusBarLongPress.isEnabled() && event.getActionMasked() != MotionEvent.ACTION_DOWN @@ -5105,6 +5101,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring."); return false; } + if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch( + event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) { + if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { + mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event"); + } + return true; + } if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); handled = true; diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt index a8026cd61aaf..6fb9b1fe8873 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt @@ -20,7 +20,11 @@ import android.content.Context import android.content.res.Resources import android.view.LayoutInflater import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY +import com.android.systemui.common.ui.ConfigurationState +import com.android.systemui.common.ui.ConfigurationStateImpl import com.android.systemui.common.ui.GlobalConfig +import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl import com.android.systemui.dagger.SysUISingleton import com.android.systemui.res.R import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround @@ -92,9 +96,41 @@ object ShadeDisplayAwareModule { @ShadeDisplayAware @SysUISingleton fun provideShadeWindowConfigurationForwarder( - @ShadeDisplayAware shadeConfigurationController: ConfigurationController, + @ShadeDisplayAware shadeConfigurationController: ConfigurationController ): ConfigurationForwarder { ShadeWindowGoesAround.isUnexpectedlyInLegacyMode() return shadeConfigurationController } + + @SysUISingleton + @Provides + @ShadeDisplayAware + fun provideShadeDisplayAwareConfigurationState( + factory: ConfigurationStateImpl.Factory, + @ShadeDisplayAware configurationController: ConfigurationController, + @ShadeDisplayAware context: Context, + @GlobalConfig configurationState: ConfigurationState, + ): ConfigurationState { + return if (ShadeWindowGoesAround.isEnabled) { + factory.create(context, configurationController) + } else { + configurationState + } + } + + @SysUISingleton + @Provides + @ShadeDisplayAware + fun provideShadeDisplayAwareConfigurationRepository( + factory: ConfigurationRepositoryImpl.Factory, + @ShadeDisplayAware configurationController: ConfigurationController, + @ShadeDisplayAware context: Context, + @GlobalConfig globalConfigurationRepository: ConfigurationRepository, + ): ConfigurationRepository { + return if (ShadeWindowGoesAround.isEnabled) { + factory.create(context, configurationController) + } else { + globalConfigurationRepository + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index 72a465030bf5..2348a110eb3a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -49,10 +49,7 @@ import dagger.Provides import javax.inject.Provider /** Module for classes related to the notification shade. */ -@Module( - includes = - [StartShadeModule::class, ShadeViewProviderModule::class] -) +@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class]) abstract class ShadeModule { companion object { @Provides diff --git a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt index 6fb3ca5f86d2..ae36e81c7b1f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt @@ -25,7 +25,7 @@ import javax.inject.Inject /** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */ @SysUISingleton -class LongPressGestureDetector +class StatusBarLongPressGestureDetector @Inject constructor(context: Context, val shadeViewController: ShadeViewController) { val gestureDetector = diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt index 5629938deeb0..ef62d2da9589 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -15,9 +15,7 @@ */ package com.android.systemui.shade.data.repository -import android.content.Context import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -182,8 +180,7 @@ interface ShadeRepository { /** Business logic for shade interactions */ @SysUISingleton -class ShadeRepositoryImpl @Inject constructor() : - ShadeRepository { +class ShadeRepositoryImpl @Inject constructor() : ShadeRepository { private val _qsExpansion = MutableStateFlow(0f) @Deprecated("Use ShadeInteractor.qsExpansion instead") override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt index e5d08a0ac977..44f29119a1a3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade.domain.startable import android.content.Context +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.CoreStartable import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.common.ui.data.repository.ConfigurationRepository @@ -42,7 +43,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onStart -import com.android.app.tracing.coroutines.launchTraced as launch @SysUISingleton class ShadeStartable @@ -51,7 +51,7 @@ constructor( @Application private val applicationScope: CoroutineScope, @ShadeDisplayAware private val context: Context, @ShadeTouchLog private val touchLog: LogBuffer, - private val configurationRepository: ConfigurationRepository, + @ShadeDisplayAware private val configurationRepository: ConfigurationRepository, private val shadeRepository: ShadeRepository, private val splitShadeStateController: SplitShadeStateController, private val scrimShadeTransitionController: ScrimShadeTransitionController, 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/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt index 3abbc6e0d1cb..f441fd644c17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt @@ -23,6 +23,7 @@ import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.res.R import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener +import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions import com.android.systemui.statusbar.phone.PhoneStatusBarView import com.android.systemui.statusbar.phone.PhoneStatusBarViewController @@ -71,7 +72,10 @@ interface StatusBarInitializer : CoreStartable { } interface Factory { - fun create(statusBarWindowController: StatusBarWindowController): StatusBarInitializer + fun create( + statusBarWindowController: StatusBarWindowController, + statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository, + ): StatusBarInitializer } } @@ -79,6 +83,7 @@ class StatusBarInitializerImpl @AssistedInject constructor( @Assisted private val statusBarWindowController: StatusBarWindowController, + @Assisted private val statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository, private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>, private val statusBarRootFactory: StatusBarRootFactory, private val componentFactory: HomeStatusBarComponent.Factory, @@ -127,7 +132,7 @@ constructor( val phoneStatusBarView = cv.findViewById<PhoneStatusBarView>(R.id.status_bar) component = componentFactory.create(phoneStatusBarView).also { component -> - // CollapsedStatusBarFragment used to be responsible initializting + // CollapsedStatusBarFragment used to be responsible initializing component.init() statusBarViewUpdatedListener?.onStatusBarViewUpdated( @@ -135,8 +140,12 @@ constructor( component.phoneStatusBarTransitions, ) - creationListeners.forEach { listener -> - listener.onStatusBarViewInitialized(component) + if (StatusBarConnectedDisplays.isEnabled) { + statusBarModePerDisplayRepository.onStatusBarViewInitialized(component) + } else { + creationListeners.forEach { listener -> + listener.onStatusBarViewInitialized(component) + } } } } @@ -184,7 +193,8 @@ constructor( @AssistedFactory interface Factory : StatusBarInitializer.Factory { override fun create( - statusBarWindowController: StatusBarWindowController + statusBarWindowController: StatusBarWindowController, + statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository, ): StatusBarInitializerImpl } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt index 041f0b0fdf93..4f815c1f0b31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt @@ -22,6 +22,7 @@ import com.android.systemui.display.data.repository.DisplayRepository 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.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -37,6 +38,7 @@ constructor( displayRepository: DisplayRepository, private val factory: StatusBarInitializer.Factory, private val statusBarWindowControllerStore: StatusBarWindowControllerStore, + private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore, ) : StatusBarInitializerStore, PerDisplayStoreImpl<StatusBarInitializer>(backgroundApplicationScope, displayRepository) { @@ -47,7 +49,8 @@ constructor( override fun createInstanceForDisplay(displayId: Int): StatusBarInitializer { return factory.create( - statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId) + statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId), + statusBarModePerDisplayRepository = statusBarModeRepositoryStore.forDisplay(displayId), ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index f65ae67efbf1..434120051039 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -26,6 +26,7 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.data.StatusBarDataLayerModule +import com.android.systemui.statusbar.data.repository.LightBarControllerStore import com.android.systemui.statusbar.phone.LightBarController import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl @@ -55,26 +56,26 @@ import dagger.multibindings.IntoMap * [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.). */ @Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class]) -abstract class StatusBarModule { +interface StatusBarModule { @Binds @IntoMap @ClassKey(OngoingCallController::class) - abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable + fun bindOngoingCallController(impl: OngoingCallController): CoreStartable @Binds @IntoMap @ClassKey(LightBarController::class) - abstract fun bindLightBarController(impl: LightBarController): CoreStartable + fun lightBarControllerAsCoreStartable(controller: LightBarController): CoreStartable @Binds @IntoMap @ClassKey(StatusBarSignalPolicy::class) - abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable + fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable @Binds @SysUISingleton - abstract fun statusBarWindowControllerFactory( + fun statusBarWindowControllerFactory( implFactory: StatusBarWindowControllerImpl.Factory ): StatusBarWindowController.Factory @@ -82,6 +83,12 @@ abstract class StatusBarModule { @Provides @SysUISingleton + fun lightBarController(store: LightBarControllerStore): LightBarController { + return store.defaultDisplay + } + + @Provides + @SysUISingleton fun windowControllerStore( multiDisplayImplLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>, singleDisplayImplLazy: Lazy<SingleDisplayStatusBarWindowControllerStore>, 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 f2d926fc22b1..39de28e7cb49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.data import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule +import com.android.systemui.statusbar.data.repository.LightBarControllerStoreModule import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerModule import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStoreModule @@ -28,6 +29,7 @@ import dagger.Module includes = [ KeyguardStatusBarRepositoryModule::class, + LightBarControllerStoreModule::class, RemoteInputRepositoryModule::class, StatusBarConfigurationControllerModule::class, StatusBarContentInsetsProviderStoreModule::class, 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 new file mode 100644 index 000000000000..ff50e3100672 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.data.repository + +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.DisplayScopeRepository +import com.android.systemui.display.data.repository.PerDisplayStore +import com.android.systemui.display.data.repository.PerDisplayStoreImpl +import com.android.systemui.statusbar.phone.LightBarController +import com.android.systemui.statusbar.phone.LightBarControllerImpl +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope + +/** Provides per display instances of [LightBarController]. */ +interface LightBarControllerStore : PerDisplayStore<LightBarController> + +@SysUISingleton +class LightBarControllerStoreImpl +@Inject +constructor( + @Background backgroundApplicationScope: CoroutineScope, + displayRepository: DisplayRepository, + private val factory: LightBarControllerImpl.Factory, + private val displayScopeRepository: DisplayScopeRepository, + private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore, +) : + LightBarControllerStore, + PerDisplayStoreImpl<LightBarController>(backgroundApplicationScope, displayRepository) { + + override fun createInstanceForDisplay(displayId: Int): LightBarController { + return factory + .create( + displayId, + displayScopeRepository.scopeForDisplay(displayId), + statusBarModeRepositoryStore.forDisplay(displayId), + ) + .also { it.start() } + } + + override suspend fun onDisplayRemovalAction(instance: LightBarController) { + instance.stop() + } + + override val instanceClass = LightBarController::class.java +} + +@Module +interface LightBarControllerStoreModule { + + @Binds fun store(impl: LightBarControllerStoreImpl): LightBarControllerStore + + @Binds + @IntoMap + @ClassKey(LightBarControllerStore::class) + fun storeAsCoreStartable(impl: LightBarControllerStoreImpl): CoreStartable +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt index 44bee1d784fc..cc91e2dc3a25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt @@ -27,7 +27,7 @@ import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BA import android.view.WindowInsetsController.Appearance import com.android.internal.statusbar.LetterboxDetails import com.android.internal.view.AppearanceRegion -import com.android.systemui.Dumpable +import com.android.systemui.CoreStartable import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener @@ -59,7 +59,7 @@ import kotlinx.coroutines.flow.stateIn * Note: These status bar modes are status bar *window* states that are sent to us from * WindowManager, not determined internally. */ -interface StatusBarModePerDisplayRepository { +interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener, CoreStartable { /** * True if the status bar window is showing transiently and will disappear soon, and false * otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR @@ -104,6 +104,12 @@ interface StatusBarModePerDisplayRepository { * determined internally instead. */ fun clearTransient() + + /** + * Called when the [StatusBarModePerDisplayRepository] should stop doing any work and clean up + * if needed. + */ + fun stop() } class StatusBarModePerDisplayRepositoryImpl @@ -114,7 +120,7 @@ constructor( private val commandQueue: CommandQueue, private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator, ongoingCallRepository: OngoingCallRepository, -) : StatusBarModePerDisplayRepository, OnStatusBarViewInitializedListener, Dumpable { +) : StatusBarModePerDisplayRepository { private val commandQueueCallback = object : CommandQueue.Callbacks { @@ -163,10 +169,14 @@ constructor( } } - fun start() { + override fun start() { commandQueue.addCallback(commandQueueCallback) } + override fun stop() { + commandQueue.removeCallback(commandQueueCallback) + } + private val _isTransientShown = MutableStateFlow(false) override val isTransientShown: StateFlow<Boolean> = _isTransientShown.asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt index 2c9fa25d8535..143e99823860 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt @@ -18,21 +18,54 @@ package com.android.systemui.statusbar.data.repository import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.DisplayId +import com.android.systemui.display.data.repository.DisplayRepository +import com.android.systemui.display.data.repository.PerDisplayStore +import com.android.systemui.display.data.repository.PerDisplayStoreImpl +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.core.StatusBarInitializer import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent import dagger.Binds +import dagger.Lazy import dagger.Module +import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap import dagger.multibindings.IntoSet import java.io.PrintWriter import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope -interface StatusBarModeRepositoryStore { - val defaultDisplay: StatusBarModePerDisplayRepository +interface StatusBarModeRepositoryStore : PerDisplayStore<StatusBarModePerDisplayRepository> - fun forDisplay(displayId: Int): StatusBarModePerDisplayRepository +@SysUISingleton +class MultiDisplayStatusBarModeRepositoryStore +@Inject +constructor( + @Background backgroundApplicationScope: CoroutineScope, + private val factory: StatusBarModePerDisplayRepositoryFactory, + displayRepository: DisplayRepository, +) : + StatusBarModeRepositoryStore, + PerDisplayStoreImpl<StatusBarModePerDisplayRepository>( + backgroundApplicationScope, + displayRepository, + ) { + + init { + StatusBarConnectedDisplays.assertInNewMode() + } + + override fun createInstanceForDisplay(displayId: Int): StatusBarModePerDisplayRepository { + return factory.create(displayId).also { it.start() } + } + + override suspend fun onDisplayRemovalAction(instance: StatusBarModePerDisplayRepository) { + instance.stop() + } + + override val instanceClass = StatusBarModePerDisplayRepository::class.java } @SysUISingleton @@ -47,10 +80,7 @@ constructor( StatusBarInitializer.OnStatusBarViewInitializedListener { override val defaultDisplay = factory.create(displayId) - override fun forDisplay(displayId: Int) = - // TODO(b/369337087): implement per display status bar modes. - // For now just use default display instance. - defaultDisplay + override fun forDisplay(displayId: Int) = defaultDisplay override fun start() { defaultDisplay.start() @@ -66,17 +96,40 @@ constructor( } @Module -interface StatusBarModeRepositoryModule { - @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepositoryStore - - @Binds - @IntoMap - @ClassKey(StatusBarModeRepositoryStore::class) - fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable - +abstract class StatusBarModeRepositoryModule { @Binds @IntoSet - fun bindViewInitListener( + abstract fun bindViewInitListener( impl: StatusBarModeRepositoryImpl ): StatusBarInitializer.OnStatusBarViewInitializedListener + + companion object { + @Provides + @SysUISingleton + @IntoMap + @ClassKey(StatusBarModeRepositoryStore::class) + fun storeAsCoreStartable( + singleDisplayLazy: Lazy<StatusBarModeRepositoryImpl>, + multiDisplayLazy: Lazy<MultiDisplayStatusBarModeRepositoryStore>, + ): CoreStartable { + return if (StatusBarConnectedDisplays.isEnabled) { + multiDisplayLazy.get() + } else { + singleDisplayLazy.get() + } + } + + @Provides + @SysUISingleton + fun store( + singleDisplayLazy: Lazy<StatusBarModeRepositoryImpl>, + multiDisplayLazy: Lazy<MultiDisplayStatusBarModeRepositoryStore>, + ): StatusBarModeRepositoryStore { + return if (StatusBarConnectedDisplays.isEnabled) { + multiDisplayLazy.get() + } else { + singleDisplayLazy.get() + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index 77ec65bfad6f..9a779300de97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -49,12 +49,12 @@ class ConversationNotificationProcessor @Inject constructor( private val launcherApps: LauncherApps, - private val conversationNotificationManager: ConversationNotificationManager + private val conversationNotificationManager: ConversationNotificationManager, ) { fun processNotification( entry: NotificationEntry, recoveredBuilder: Notification.Builder, - logger: NotificationRowContentBinderLogger + logger: NotificationRowContentBinderLogger, ): Notification.MessagingStyle? { val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return null messagingStyle.conversationType = @@ -83,7 +83,7 @@ constructor( private val notifCollection: CommonNotifCollection, private val bindEventManager: BindEventManager, private val headsUpManager: HeadsUpManager, - private val statusBarStateController: StatusBarStateController + private val statusBarStateController: StatusBarStateController, ) { private var isStatusBarExpanded = false @@ -118,7 +118,8 @@ constructor( .flatMap { layout -> layout.allViews.asSequence() } .flatMap { view -> (view as? ConversationLayout)?.messagingGroups?.asSequence() - ?: (view as? MessagingLayout)?.messagingGroups?.asSequence() ?: emptySequence() + ?: (view as? MessagingLayout)?.messagingGroups?.asSequence() + ?: emptySequence() } .flatMap { messagingGroup -> messagingGroup.messageContainer.children } .mapNotNull { view -> @@ -144,7 +145,7 @@ constructor( bindEventManager: BindEventManager, @ShadeDisplayAware private val context: Context, private val notifCollection: CommonNotifCollection, - @Main private val mainHandler: Handler + @Main private val mainHandler: Handler, ) { // Need this state to be thread safe, since it's accessed from the ui thread // (NotificationEntryListener) and a bg thread (NotificationRowContentBinder) @@ -175,7 +176,7 @@ constructor( // the notif has been moved in the shade mainHandler.postDelayed( { layout.setIsImportantConversation(important, true) }, - IMPORTANCE_ANIMATION_DELAY.toLong() + IMPORTANCE_ANIMATION_DELAY.toLong(), ) } else { layout.setIsImportantConversation(important, false) @@ -233,8 +234,7 @@ constructor( state?.run { if (shouldIncrementUnread(recoveredBuilder)) unreadCount + 1 else unreadCount - } - ?: 1 + } ?: 1 ConversationState(newCount, entry.sbn.notification) }!! .unreadCount diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt index de868d45e64f..df8e56eb4102 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt @@ -33,29 +33,30 @@ import javax.inject.Inject * they are fully attached. */ @CoordinatorScope -class RowAppearanceCoordinator @Inject internal constructor( +class RowAppearanceCoordinator +@Inject +internal constructor( @ShadeDisplayAware context: Context, private var mAssistantFeedbackController: AssistantFeedbackController, - private var mSectionStyleProvider: SectionStyleProvider + private var mSectionStyleProvider: SectionStyleProvider, ) : Coordinator { private var entryToExpand: NotificationEntry? = null /** - * `true` if notifications not part of a group should by default be rendered in their - * expanded state. If `false`, then only the first notification will be expanded if - * possible. + * `true` if notifications not part of a group should by default be rendered in their expanded + * state. If `false`, then only the first notification will be expanded if possible. */ private val mAlwaysExpandNonGroupedNotification = context.resources.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications) /** - * `true` if the first non-group expandable notification should be expanded automatically - * when possible. If `false`, then the first non-group expandable notification should not - * be expanded. + * `true` if the first non-group expandable notification should be expanded automatically when + * possible. If `false`, then the first non-group expandable notification should not be + * expanded. */ private val mAutoExpandFirstNotification = - context.resources.getBoolean(R.bool.config_autoExpandFirstNotification) + context.resources.getBoolean(R.bool.config_autoExpandFirstNotification) override fun attach(pipeline: NotifPipeline) { pipeline.addOnBeforeRenderListListener(::onBeforeRenderList) @@ -63,17 +64,20 @@ class RowAppearanceCoordinator @Inject internal constructor( } private fun onBeforeRenderList(list: List<ListEntry>) { - entryToExpand = list.firstOrNull()?.representativeEntry?.takeIf { entry -> - !mSectionStyleProvider.isMinimizedSection(entry.section!!) - } + entryToExpand = + list.firstOrNull()?.representativeEntry?.takeIf { entry -> + !mSectionStyleProvider.isMinimizedSection(entry.section!!) + } } private fun onAfterRenderEntry(entry: NotificationEntry, controller: NotifRowController) { // If mAlwaysExpandNonGroupedNotification is false, then only expand the // very first notification if it's not a child of grouped notifications and when // mAutoExpandFirstNotification is true. - controller.setSystemExpanded(mAlwaysExpandNonGroupedNotification || - (mAutoExpandFirstNotification && entry == entryToExpand)) + controller.setSystemExpanded( + mAlwaysExpandNonGroupedNotification || + (mAutoExpandFirstNotification && entry == entryToExpand) + ) // Show/hide the feedback icon controller.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry)) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt index db778b801dbc..2d1eccdf1abd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt @@ -17,10 +17,12 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.util.Log +import com.android.app.tracing.traceSection import com.android.internal.widget.MessagingGroup import com.android.internal.widget.MessagingMessage import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener import com.android.systemui.statusbar.notification.ColorUpdateLogger @@ -29,17 +31,17 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger import com.android.systemui.statusbar.notification.row.NotificationGutsManager import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.Compile -import com.android.app.tracing.traceSection -import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject /** - * A coordinator which ensures that notifications within the new pipeline are correctly inflated - * for the current uiMode and screen properties; additionally deferring those changes when a user - * change is in progress until that process has completed. + * A coordinator which ensures that notifications within the new pipeline are correctly inflated for + * the current uiMode and screen properties; additionally deferring those changes when a user change + * is in progress until that process has completed. */ @CoordinatorScope -class ViewConfigCoordinator @Inject internal constructor( +class ViewConfigCoordinator +@Inject +internal constructor( @ShadeDisplayAware private val mConfigurationController: ConfigurationController, private val mLockscreenUserManager: NotificationLockscreenUserManager, private val mGutsManager: NotificationGutsManager, @@ -52,28 +54,32 @@ class ViewConfigCoordinator @Inject internal constructor( private var mDispatchUiModeChangeOnUserSwitched = false private var mPipeline: NotifPipeline? = null - private val mKeyguardUpdateCallback = object : KeyguardUpdateMonitorCallback() { - override fun onUserSwitching(userId: Int) { - colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitching()") - log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" } - mIsSwitchingUser = true - } + private val mKeyguardUpdateCallback = + object : KeyguardUpdateMonitorCallback() { + override fun onUserSwitching(userId: Int) { + colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitching()") + log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" } + mIsSwitchingUser = true + } - override fun onUserSwitchComplete(userId: Int) { - colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitchComplete()") - log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" } - mIsSwitchingUser = false - applyChangesOnUserSwitched() + override fun onUserSwitchComplete(userId: Int) { + colorUpdateLogger.logTriggerEvent( + "VCC.mKeyguardUpdateCallback.onUserSwitchComplete()" + ) + log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" } + mIsSwitchingUser = false + applyChangesOnUserSwitched() + } } - } - private val mUserChangedListener = object : UserChangedListener { - override fun onUserChanged(userId: Int) { - colorUpdateLogger.logTriggerEvent("VCC.mUserChangedListener.onUserChanged()") - log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" } - applyChangesOnUserSwitched() + private val mUserChangedListener = + object : UserChangedListener { + override fun onUserChanged(userId: Int) { + colorUpdateLogger.logTriggerEvent("VCC.mUserChangedListener.onUserChanged()") + log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" } + applyChangesOnUserSwitched() + } } - } override fun attach(pipeline: NotifPipeline) { mPipeline = pipeline @@ -87,8 +93,8 @@ class ViewConfigCoordinator @Inject internal constructor( log { val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser "ViewConfigCoordinator.onDensityOrFontScaleChanged()" + - " isSwitchingUser=$mIsSwitchingUser" + - " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser" + " isSwitchingUser=$mIsSwitchingUser" + + " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser" } MessagingMessage.dropCache() MessagingGroup.dropCache() @@ -104,8 +110,8 @@ class ViewConfigCoordinator @Inject internal constructor( log { val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser "ViewConfigCoordinator.onUiModeChanged()" + - " isSwitchingUser=$mIsSwitchingUser" + - " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser" + " isSwitchingUser=$mIsSwitchingUser" + + " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser" } if (!mIsSwitchingUser) { updateNotificationsOnUiModeChanged() @@ -132,13 +138,13 @@ class ViewConfigCoordinator @Inject internal constructor( } private fun updateNotificationsOnUiModeChanged() { - colorUpdateLogger.logEvent("VCC.updateNotificationsOnUiModeChanged()", - "mode=" + mConfigurationController.nightModeName) + colorUpdateLogger.logEvent( + "VCC.updateNotificationsOnUiModeChanged()", + "mode=" + mConfigurationController.nightModeName, + ) log { "ViewConfigCoordinator.updateNotificationsOnUiModeChanged()" } traceSection("updateNotifOnUiModeChanged") { - mPipeline?.allNotifs?.forEach { entry -> - entry.row?.onUiModeChanged() - } + mPipeline?.allNotifs?.forEach { entry -> entry.row?.onUiModeChanged() } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt index cab4c1c88b2c..3c838e5b707e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.collection.render import android.content.Context import android.view.View +import com.android.app.tracing.traceSection +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry @@ -26,8 +28,6 @@ import com.android.systemui.statusbar.notification.collection.PipelineDumpable import com.android.systemui.statusbar.notification.collection.PipelineDumper import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider import com.android.systemui.statusbar.notification.stack.NotificationListContainer -import com.android.app.tracing.traceSection -import com.android.systemui.shade.ShadeDisplayAware import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -36,7 +36,9 @@ import dagger.assisted.AssistedInject * Responsible for building and applying the "shade node spec": the list (tree) of things that * currently populate the notification shade. */ -class ShadeViewManager @AssistedInject constructor( +class ShadeViewManager +@AssistedInject +constructor( @ShadeDisplayAware context: Context, @Assisted listContainer: NotificationListContainer, @Assisted private val stackController: NotifStackController, @@ -45,13 +47,19 @@ class ShadeViewManager @AssistedInject constructor( sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider, nodeSpecBuilderLogger: NodeSpecBuilderLogger, shadeViewDifferLogger: ShadeViewDifferLogger, - private val viewBarn: NotifViewBarn + private val viewBarn: NotifViewBarn, ) : PipelineDumpable { // We pass a shim view here because the listContainer may not actually have a view associated // with it and the differ never actually cares about the root node's view. private val rootController = RootNodeController(listContainer, View(context)) - private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager, - sectionHeaderVisibilityProvider, viewBarn, nodeSpecBuilderLogger) + private val specBuilder = + NodeSpecBuilder( + mediaContainerController, + featureManager, + sectionHeaderVisibilityProvider, + viewBarn, + nodeSpecBuilderLogger, + ) private val viewDiffer = ShadeViewDiffer(rootController, shadeViewDifferLogger) /** Method for attaching this manager to the pipeline. */ @@ -59,34 +67,36 @@ class ShadeViewManager @AssistedInject constructor( renderStageManager.setViewRenderer(viewRenderer) } - override fun dumpPipeline(d: PipelineDumper) = with(d) { - dump("rootController", rootController) - dump("specBuilder", specBuilder) - dump("viewDiffer", viewDiffer) - } + override fun dumpPipeline(d: PipelineDumper) = + with(d) { + dump("rootController", rootController) + dump("specBuilder", specBuilder) + dump("viewDiffer", viewDiffer) + } - private val viewRenderer = object : NotifViewRenderer { + private val viewRenderer = + object : NotifViewRenderer { - override fun onRenderList(notifList: List<ListEntry>) { - traceSection("ShadeViewManager.onRenderList") { - viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList)) + override fun onRenderList(notifList: List<ListEntry>) { + traceSection("ShadeViewManager.onRenderList") { + viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList)) + } } - } - override fun getStackController(): NotifStackController = stackController + override fun getStackController(): NotifStackController = stackController - override fun getGroupController(group: GroupEntry): NotifGroupController = - viewBarn.requireGroupController(group.requireSummary) + override fun getGroupController(group: GroupEntry): NotifGroupController = + viewBarn.requireGroupController(group.requireSummary) - override fun getRowController(entry: NotificationEntry): NotifRowController = - viewBarn.requireRowController(entry) - } + override fun getRowController(entry: NotificationEntry): NotifRowController = + viewBarn.requireRowController(entry) + } } @AssistedFactory interface ShadeViewManagerFactory { fun create( listContainer: NotificationListContainer, - stackController: NotifStackController + stackController: NotifStackController, ): ShadeViewManager } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt index 925d4a588f09..4c2512988f4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.dagger +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsModule import com.android.systemui.statusbar.notification.row.NotificationRowModule import dagger.Module @@ -23,5 +24,12 @@ import dagger.Module * A module that includes the standard notifications classes that most SysUI variants need. Variants * are free to not include this module and instead write a custom notifications module. */ -@Module(includes = [NotificationsModule::class, NotificationRowModule::class]) +@Module( + includes = + [ + NotificationsModule::class, + NotificationRowModule::class, + PromotedNotificationsModule::class, + ] +) object ReferenceNotificationsModule diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt index 7d374b051b76..dbe58337de11 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt @@ -16,12 +16,12 @@ package com.android.systemui.statusbar.notification.data +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.settings.SecureSettingsRepositoryModule -import com.android.systemui.settings.SystemSettingsRepositoryModule +import com.android.systemui.settings.UserSettingsRepositoryModule import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository @@ -32,9 +32,8 @@ import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import com.android.app.tracing.coroutines.launchTraced as launch -@Module(includes = [SecureSettingsRepositoryModule::class, SystemSettingsRepositoryModule::class]) +@Module(includes = [UserSettingsRepositoryModule::class]) object NotificationSettingsRepositoryModule { @Provides @SysUISingleton @@ -48,7 +47,8 @@ object NotificationSettingsRepositoryModule { backgroundScope, backgroundDispatcher, secureSettingsRepository, - systemSettingsRepository) + systemSettingsRepository, + ) @Provides @IntoMap @@ -57,7 +57,7 @@ object NotificationSettingsRepositoryModule { fun provideCoreStartable( @Application applicationScope: CoroutineScope, repository: NotificationSettingsRepository, - logger: VisualInterruptionDecisionLogger + logger: VisualInterruptionDecisionLogger, ) = CoreStartable { applicationScope.launch { repository.isCooldownEnabled.collect { value -> logger.logCooldownSetting(value) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index 697a6ce52ba9..cff5bef9fe69 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -83,6 +83,9 @@ constructor( // TODO(b/364653005): [ongoingCallNotification] should be incorporated into this flow // instead of being separate. topLevelRepresentativeNotifications + .map { notifs -> notifs.filter { it.isPromoted } } + .distinctUntilChanged() + .flowOn(backgroundDispatcher) } else { flowOf(emptyList()) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt index 10084517ec19..23da90d426c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel @@ -50,6 +51,7 @@ class RenderNotificationListInteractor constructor( private val repository: ActiveNotificationListRepository, private val sectionStyleProvider: SectionStyleProvider, + private val promotedNotificationsProvider: PromotedNotificationsProvider, ) { /** * Sets the current list of rendered notification entries as displayed in the notification list. @@ -57,7 +59,11 @@ constructor( fun setRenderedList(entries: List<ListEntry>) { traceSection("RenderNotificationListInteractor.setRenderedList") { repository.activeNotifications.update { existingModels -> - buildActiveNotificationsStore(existingModels, sectionStyleProvider) { + buildActiveNotificationsStore( + existingModels, + sectionStyleProvider, + promotedNotificationsProvider, + ) { entries.forEach(::addListEntry) setRankingsMap(entries) } @@ -69,13 +75,21 @@ constructor( private fun buildActiveNotificationsStore( existingModels: ActiveNotificationsStore, sectionStyleProvider: SectionStyleProvider, - block: ActiveNotificationsStoreBuilder.() -> Unit + promotedNotificationsProvider: PromotedNotificationsProvider, + block: ActiveNotificationsStoreBuilder.() -> Unit, ): ActiveNotificationsStore = - ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build() + ActiveNotificationsStoreBuilder( + existingModels, + sectionStyleProvider, + promotedNotificationsProvider, + ) + .apply(block) + .build() private class ActiveNotificationsStoreBuilder( private val existingModels: ActiveNotificationsStore, private val sectionStyleProvider: SectionStyleProvider, + private val promotedNotificationsProvider: PromotedNotificationsProvider, ) { private val builder = ActiveNotificationsStore.Builder() @@ -96,7 +110,7 @@ private class ActiveNotificationsStoreBuilder( existingModels.createOrReuse( key = entry.key, summary = summaryModel, - children = childModels + children = childModels, ) ) } @@ -141,6 +155,7 @@ private class ActiveNotificationsStoreBuilder( key = key, groupKey = sbn.groupKey, whenTime = sbn.notification.`when`, + isPromoted = promotedNotificationsProvider.shouldPromote(this), isAmbient = sectionStyleProvider.isMinimized(this), isRowDismissed = isRowDismissed, isSilent = sectionStyleProvider.isSilent(this), @@ -166,6 +181,7 @@ private fun ActiveNotificationsStore.createOrReuse( key: String, groupKey: String?, whenTime: Long, + isPromoted: Boolean, isAmbient: Boolean, isRowDismissed: Boolean, isSilent: Boolean, @@ -189,6 +205,7 @@ private fun ActiveNotificationsStore.createOrReuse( key = key, groupKey = groupKey, whenTime = whenTime, + isPromoted = isPromoted, isAmbient = isAmbient, isRowDismissed = isRowDismissed, isSilent = isSilent, @@ -212,6 +229,7 @@ private fun ActiveNotificationsStore.createOrReuse( key = key, groupKey = groupKey, whenTime = whenTime, + isPromoted = isPromoted, isAmbient = isAmbient, isRowDismissed = isRowDismissed, isSilent = isSilent, @@ -236,6 +254,7 @@ private fun ActiveNotificationModel.isCurrent( key: String, groupKey: String?, whenTime: Long, + isPromoted: Boolean, isAmbient: Boolean, isRowDismissed: Boolean, isSilent: Boolean, @@ -258,6 +277,7 @@ private fun ActiveNotificationModel.isCurrent( key != this.key -> false groupKey != this.groupKey -> false whenTime != this.whenTime -> false + isPromoted != this.isPromoted -> false isAmbient != this.isAmbient -> false isRowDismissed != this.isRowDismissed -> false isSilent != this.isSilent -> false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index 96f47e5fdd52..a0515ca92cdd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -459,8 +459,12 @@ public class FooterView extends StackScrollerDecorView { Resources.Theme theme = mContext.getTheme(); final @ColorInt int onSurface = Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.materialColorOnSurface); + // Same resource, separate drawables to prevent touch effects from showing on the wrong + // button. final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background); - final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background); + final Drawable settingsBg = theme.getDrawable(R.drawable.notif_footer_btn_background); + final Drawable historyBg = NotifRedesignFooter.isEnabled() + ? theme.getDrawable(R.drawable.notif_footer_btn_background) : null; final @ColorInt int scHigh; if (!notificationFooterBackgroundTintOptimization()) { scHigh = Utils.getColorAttrDefaultColor(mContext, @@ -468,7 +472,10 @@ public class FooterView extends StackScrollerDecorView { if (scHigh != 0) { final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP); clearAllBg.setColorFilter(bgColorFilter); - manageBg.setColorFilter(bgColorFilter); + settingsBg.setColorFilter(bgColorFilter); + if (NotifRedesignFooter.isEnabled()) { + historyBg.setColorFilter(bgColorFilter); + } } } else { scHigh = 0; @@ -476,13 +483,13 @@ public class FooterView extends StackScrollerDecorView { mClearAllButton.setBackground(clearAllBg); mClearAllButton.setTextColor(onSurface); if (NotifRedesignFooter.isEnabled()) { - mSettingsButton.setBackground(manageBg); + mSettingsButton.setBackground(settingsBg); mSettingsButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface)); - mHistoryButton.setBackground(manageBg); + mHistoryButton.setBackground(historyBg); mHistoryButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface)); } else { - mManageOrHistoryButton.setBackground(manageBg); + mManageOrHistoryButton.setBackground(settingsBg); mManageOrHistoryButton.setTextColor(onSurface); } mSeenNotifsFooterTextView.setTextColor(onSurface); @@ -492,7 +499,7 @@ public class FooterView extends StackScrollerDecorView { colorUpdateLogger.logEvent("Footer.updateColors()", "textColor(onSurface)=" + hexColorString(onSurface) + " backgroundTint(surfaceContainerHigh)=" + hexColorString(scHigh) - + " background=" + DrawableDumpKt.dumpToString(manageBg)); + + " background=" + DrawableDumpKt.dumpToString(settingsBg)); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt index 71cddc99b564..52336be742cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt @@ -75,7 +75,7 @@ constructor( private val bubbles: Optional<Bubbles>, @ShadeDisplayAware private val context: Context, private val notificationManager: NotificationManager, - private val settingsInteractor: NotificationSettingsInteractor + private val settingsInteractor: NotificationSettingsInteractor, ) : VisualInterruptionDecisionProvider { init { @@ -89,7 +89,7 @@ constructor( private class DecisionImpl( override val shouldInterrupt: Boolean, - override val logReason: String + override val logReason: String, ) : Decision private data class LoggableDecision @@ -107,7 +107,7 @@ constructor( LoggableDecision( DecisionImpl( shouldInterrupt = false, - logReason = "${legacySuppressor.name}.$methodName" + logReason = "${legacySuppressor.name}.$methodName", ) ) @@ -123,7 +123,7 @@ constructor( private class FullScreenIntentDecisionImpl( val entry: NotificationEntry, - private val fsiDecision: FullScreenIntentDecisionProvider.Decision + private val fsiDecision: FullScreenIntentDecisionProvider.Decision, ) : FullScreenIntentDecision, Loggable { var hasBeenLogged = false @@ -154,7 +154,7 @@ constructor( deviceProvisionedController, keyguardStateController, powerManager, - statusBarStateController + statusBarStateController, ) private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>() @@ -197,7 +197,7 @@ constructor( context, notificationManager, logger, - systemSettings + systemSettings, ) ) avalancheProvider.register() @@ -290,7 +290,7 @@ constructor( private fun logDecision( type: VisualInterruptionType, entry: NotificationEntry, - loggableDecision: LoggableDecision + loggableDecision: LoggableDecision, ) { if (!loggableDecision.isSpammy || logger.spew) { logger.logDecision(type.name, entry, loggableDecision.decision) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt new file mode 100644 index 000000000000..632421980772 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.promoted + +import android.app.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the promoted ongoing notifications UI flag state. */ +@Suppress("NOTHING_TO_INLINE") +object PromotedNotificationUi { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.uiRichOngoing() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is not enabled to ensure that the refactor author catches issues in testing. + * Caution!! Using this check incorrectly will cause crashes in nextfood builds! + */ + @JvmStatic + inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt index 6199a83f9075..4be12bd44a29 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt @@ -14,25 +14,17 @@ * limitations under the License. */ -package com.android.systemui.settings +package com.android.systemui.statusbar.notification.promoted -import android.content.ContentResolver import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository -import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl +import dagger.Binds import dagger.Module -import dagger.Provides -import kotlinx.coroutines.CoroutineDispatcher @Module -object SecureSettingsRepositoryModule { - @JvmStatic - @Provides +abstract class PromotedNotificationsModule { + @Binds @SysUISingleton - fun provideSecureSettingsRepository( - contentResolver: ContentResolver, - @Background backgroundDispatcher: CoroutineDispatcher, - ): SecureSettingsRepository = - SecureSettingsRepositoryImpl(contentResolver, backgroundDispatcher) + abstract fun bindPromotedNotificationsProvider( + impl: PromotedNotificationsProviderImpl + ): PromotedNotificationsProvider } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt new file mode 100644 index 000000000000..691dc6f5ccac --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.promoted + +import android.app.Notification.FLAG_PROMOTED_ONGOING +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import javax.inject.Inject + +/** A provider for making decisions on which notifications should be promoted. */ +interface PromotedNotificationsProvider { + /** Returns true if the given notification should be promoted and false otherwise. */ + fun shouldPromote(entry: NotificationEntry): Boolean +} + +@SysUISingleton +open class PromotedNotificationsProviderImpl @Inject constructor() : PromotedNotificationsProvider { + override fun shouldPromote(entry: NotificationEntry): Boolean { + if (!PromotedNotificationUi.isEnabled) { + return false + } + return (entry.sbn.notification.flags and FLAG_PROMOTED_ONGOING) != 0 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt index e233deffe42f..916414548fbd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt @@ -25,6 +25,7 @@ import android.util.Dumpable import android.util.Log import android.util.Size import androidx.annotation.MainThread +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.R import com.android.internal.widget.NotificationDrawableConsumer import com.android.internal.widget.NotificationIconManager @@ -45,7 +46,6 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext private const val TAG = "BigPicImageLoader" @@ -67,7 +67,7 @@ constructor( private val statsManager: BigPictureStatsManager, @Application private val scope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, - @Background private val bgDispatcher: CoroutineDispatcher + @Background private val bgDispatcher: CoroutineDispatcher, ) : NotificationIconManager, Dumpable { private var lastLoadingJob: Job? = null @@ -153,7 +153,7 @@ constructor( private fun checkPlaceHolderSizeForDrawable( displayedState: DrawableState, - newDrawable: Drawable + newDrawable: Drawable, ) { if (displayedState is PlaceHolder) { val (oldWidth, oldHeight) = displayedState.drawableSize @@ -163,7 +163,7 @@ constructor( Log.e( TAG, "Mismatch in dimensions, when replacing PlaceHolder " + - "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight." + "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight.", ) } } @@ -184,9 +184,8 @@ constructor( displayedState = drawableAndState?.second ?: Empty } - private fun startLoadingJob(icon: Icon): Job = scope.launch { - statsManager.measure { loadImage(icon) } - } + private fun startLoadingJob(icon: Icon): Job = + scope.launch { statsManager.measure { loadImage(icon) } } private suspend fun loadImage(icon: Icon) { val drawableAndState = withContext(bgDispatcher) { loadImageSync(icon) } @@ -254,9 +253,12 @@ constructor( private sealed class DrawableState(open val icon: Icon?) { data object Initial : DrawableState(null) + data object Empty : DrawableState(null) + data class PlaceHolder(override val icon: Icon, val drawableSize: Size) : DrawableState(icon) + data class FullImage(override val icon: Icon, val drawableSize: Size) : DrawableState(icon) } } @@ -298,7 +300,7 @@ private fun Size.resizeToMax(maxWidth: Int, maxHeight: Int): Size { } private val Drawable.intrinsicSize - get() = Size(/*width=*/ intrinsicWidth, /*height=*/ intrinsicHeight) + get() = Size(/* width= */ intrinsicWidth, /* height= */ intrinsicHeight) private operator fun Size.component1() = width 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/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt index cf19938aa533..19a92a2230ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt @@ -36,6 +36,8 @@ data class ActiveNotificationModel( val groupKey: String?, /** When this notification was posted. */ val whenTime: Long, + /** True if this notification should be promoted and false otherwise. */ + val isPromoted: Boolean, /** Is this entry in the ambient / minimized section (lowest priority)? */ val isAmbient: Boolean, /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index c9a0010c0de7..31e4d2cac50c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -26,7 +26,14 @@ import com.android.systemui.statusbar.notification.SourceType import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag import com.android.systemui.statusbar.notification.collection.render.MediaContainerController import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController -import com.android.systemui.statusbar.notification.dagger.* +import com.android.systemui.statusbar.notification.dagger.AlertingHeader +import com.android.systemui.statusbar.notification.dagger.IncomingHeader +import com.android.systemui.statusbar.notification.dagger.NewsHeader +import com.android.systemui.statusbar.notification.dagger.PeopleHeader +import com.android.systemui.statusbar.notification.dagger.PromoHeader +import com.android.systemui.statusbar.notification.dagger.RecsHeader +import com.android.systemui.statusbar.notification.dagger.SilentHeader +import com.android.systemui.statusbar.notification.dagger.SocialHeader import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider @@ -54,7 +61,7 @@ internal constructor( @NewsHeader private val newsHeaderController: SectionHeaderController, @SocialHeader private val socialHeaderController: SectionHeaderController, @RecsHeader private val recsHeaderController: SectionHeaderController, - @PromoHeader private val promoHeaderController: SectionHeaderController + @PromoHeader private val promoHeaderController: SectionHeaderController, ) : SectionProvider { private val configurationListener = @@ -136,14 +143,16 @@ internal constructor( override fun beginsSection(view: View, previous: View?): Boolean = view === silentHeaderView || - view === mediaControlsView || - view === peopleHeaderView || - view === alertingHeaderView || - view === incomingHeaderView || - (NotificationClassificationFlag.isEnabled && (view === newsHeaderView - || view === socialHeaderView || view === recsHeaderView - || view === promoHeaderView)) || - getBucket(view) != getBucket(previous) + view === mediaControlsView || + view === peopleHeaderView || + view === alertingHeaderView || + view === incomingHeaderView || + (NotificationClassificationFlag.isEnabled && + (view === newsHeaderView || + view === socialHeaderView || + view === recsHeaderView || + view === promoHeaderView)) || + getBucket(view) != getBucket(previous) private fun getBucket(view: View?): Int? = when { @@ -165,6 +174,7 @@ internal constructor( data class Many(val first: ExpandableView, val last: ExpandableView) : SectionBounds() data class One(val lone: ExpandableView) : SectionBounds() + object None : SectionBounds() fun addNotif(notif: ExpandableView): SectionBounds = @@ -183,7 +193,7 @@ internal constructor( private fun NotificationSection.setFirstAndLastVisibleChildren( first: ExpandableView?, - last: ExpandableView? + last: ExpandableView?, ): Boolean { val firstChanged = setFirstVisibleChild(first) val lastChanged = setLastVisibleChild(last) @@ -198,7 +208,7 @@ internal constructor( */ fun updateFirstAndLastViewsForAllSections( sections: Array<NotificationSection>, - children: List<ExpandableView> + children: List<ExpandableView>, ): Boolean { // Create mapping of bucket to section val sectionBounds = @@ -213,7 +223,7 @@ internal constructor( .foldToSparseArray( SectionBounds.None, size = sections.size, - operation = SectionBounds::addNotif + operation = SectionBounds::addNotif, ) // Build a set of the old first/last Views of the sections diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 99efba43b12f..7389086296a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -171,12 +171,14 @@ import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; +import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shade.ShadeExpansionListener; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.ShadeLogger; import com.android.systemui.shade.ShadeSurface; import com.android.systemui.shade.ShadeViewController; +import com.android.systemui.shade.StatusBarLongPressGestureDetector; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.AutoHideUiElement; @@ -295,7 +297,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }; void onStatusBarWindowStateChanged(@WindowVisibleState int state) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarConnectedDisplays.assertInLegacyMode(); mStatusBarWindowState = state; updateBubblesVisibility(); } @@ -366,6 +368,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private PhoneStatusBarViewController mPhoneStatusBarViewController; private PhoneStatusBarTransitions mStatusBarTransitions; + private final Provider<StatusBarLongPressGestureDetector> mStatusBarLongPressGestureDetector; private final AuthRippleController mAuthRippleController; @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING; private final NotificationShadeWindowController mNotificationShadeWindowController; @@ -671,6 +674,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { ShadeController shadeController, WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, StatusBarKeyguardViewManager statusBarKeyguardViewManager, + Provider<StatusBarLongPressGestureDetector> statusBarLongPressGestureDetector, ViewMediatorCallback viewMediatorCallback, InitController initController, @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler, @@ -778,6 +782,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mShadeController = shadeController; mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector; mKeyguardViewMediatorCallback = viewMediatorCallback; mInitController = initController; mPluginDependencyProvider = pluginDependencyProvider; @@ -1527,6 +1532,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // to touch outside the customizer to close it, such as on the status or nav bar. mShadeController.onStatusBarTouch(event); } + if (ShadeExpandsOnStatusBarLongPress.isEnabled() + && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { + mStatusBarLongPressGestureDetector.get().handleTouch(event); + } + return getNotificationShadeWindowView().onTouchEvent(event); }; } @@ -1589,8 +1599,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager); - - mLightBarController.setBiometricUnlockController(mBiometricUnlockController); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index be2fb68ab88d..2433b78fc183 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -50,6 +50,8 @@ import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel; import com.android.systemui.log.core.LogLevel; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; @@ -82,6 +84,8 @@ import com.android.systemui.util.settings.SecureSettings; import kotlin.Unit; +import kotlinx.coroutines.CoroutineDispatcher; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -108,6 +112,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat R.id.keyguard_hun_animator_end_tag, R.id.keyguard_hun_animator_start_tag); + private final CoroutineDispatcher mCoroutineDispatcher; private final CarrierTextController mCarrierTextController; private final ConfigurationController mConfigurationController; private final SystemStatusAnimationScheduler mAnimationScheduler; @@ -133,6 +138,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private final Object mLock = new Object(); private final KeyguardLogger mLogger; private final CommunalSceneInteractor mCommunalSceneInteractor; + private final GlanceableHubToLockscreenTransitionViewModel mHubToLockscreenTransitionViewModel; + private final LockscreenToGlanceableHubTransitionViewModel mLockscreenToHubTransitionViewModel; private View mSystemIconsContainer; private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory; @@ -249,9 +256,20 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private boolean mCommunalShowing; private final Consumer<Boolean> mCommunalConsumer = (communalShowing) -> { + updateCommunalShowing(communalShowing); + }; + + @VisibleForTesting + void updateCommunalShowing(boolean communalShowing) { mCommunalShowing = communalShowing; + + // When communal is hidden (either by transition or state change), set alpha to fully + // visible. + if (!mCommunalShowing) { + setAlpha(-1f); + } updateViewState(); - }; + } private final DisableStateTracker mDisableStateTracker; @@ -277,6 +295,15 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private boolean mShowingKeyguardHeadsUp; private StatusBarSystemEventDefaultAnimator mSystemEventAnimator; private float mSystemEventAnimatorAlpha = 1; + private final Consumer<Float> mToGlanceableHubStatusBarAlphaConsumer = (alpha) -> + updateCommunalAlphaTransition(alpha); + + private final Consumer<Float> mFromGlanceableHubStatusBarAlphaConsumer = (alpha) -> + updateCommunalAlphaTransition(alpha); + + @VisibleForTesting void updateCommunalAlphaTransition(float alpha) { + setAlpha(!mCommunalShowing || alpha == 0 ? -1 : alpha); + } /** * The alpha value to be set on the View. If -1, this value is to be ignored. @@ -285,6 +312,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat @Inject public KeyguardStatusBarViewController( + @Main CoroutineDispatcher dispatcher, KeyguardStatusBarView view, CarrierTextController carrierTextController, ConfigurationController configurationController, @@ -310,9 +338,14 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat @Background Executor backgroundExecutor, KeyguardLogger logger, StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory, - CommunalSceneInteractor communalSceneInteractor + CommunalSceneInteractor communalSceneInteractor, + GlanceableHubToLockscreenTransitionViewModel + glanceableHubToLockscreenTransitionViewModel, + LockscreenToGlanceableHubTransitionViewModel + lockscreenToGlanceableHubTransitionViewModel ) { super(view); + mCoroutineDispatcher = dispatcher; mCarrierTextController = carrierTextController; mConfigurationController = configurationController; mAnimationScheduler = animationScheduler; @@ -337,6 +370,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mBackgroundExecutor = backgroundExecutor; mLogger = logger; mCommunalSceneInteractor = communalSceneInteractor; + mHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel; + mLockscreenToHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel; mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled(); mKeyguardStateController.addCallback( @@ -418,7 +453,12 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat UserHandle.USER_ALL); updateUserSwitcher(); onThemeChanged(); - collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer); + collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer, + mCoroutineDispatcher); + collectFlow(mView, mLockscreenToHubTransitionViewModel.getStatusBarAlpha(), + mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher); + collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(), + mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher); } @Override @@ -573,7 +613,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat && !mDozing && !hideForBypass && !mDisableStateTracker.isDisabled() - && !mCommunalShowing + && (!mCommunalShowing || mExplicitAlpha != -1) ? View.VISIBLE : View.INVISIBLE; updateViewState(newAlpha, newVisibility); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt new file mode 100644 index 000000000000..a6374a66806b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.view.WindowInsetsController +import com.android.internal.colorextraction.ColorExtractor +import com.android.internal.view.AppearanceRegion +import com.android.systemui.CoreStartable + +/** Controls how light status bar flag applies to the icons. */ +interface LightBarController : CoreStartable { + + fun stop() + + fun setNavigationBar(navigationBar: LightBarTransitionsController) + + fun onNavigationBarAppearanceChanged( + @WindowInsetsController.Appearance appearance: Int, + nbModeChanged: Boolean, + navigationBarMode: Int, + navbarColorManagedByIme: Boolean, + ) + + fun onNavigationBarModeChanged(newBarMode: Int) + + fun setQsCustomizing(customizing: Boolean) + + /** Set if Quick Settings is fully expanded, which affects notification scrim visibility. */ + fun setQsExpanded(expanded: Boolean) + + /** Set if Global Actions dialog is visible, which requires dark mode (light buttons). */ + fun setGlobalActionsVisible(visible: Boolean) + + /** + * Controls the light status bar temporarily for back navigation. + * + * @param appearance the customized appearance. + */ + fun customizeStatusBarAppearance(appearance: AppearanceRegion) + + /** + * Sets whether the direct-reply is in use or not. + * + * @param directReplying `true` when the direct-reply is in-use. + */ + fun setDirectReplying(directReplying: Boolean) + + fun setScrimState( + scrimState: ScrimState, + scrimBehindAlpha: Float, + scrimInFrontColor: ColorExtractor.GradientColors, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java index a33996b99900..edc1f88b7579 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java @@ -11,7 +11,7 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ package com.android.systemui.statusbar.phone; @@ -22,9 +22,9 @@ import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT; -import android.content.Context; import android.graphics.Rect; import android.util.Log; +import android.view.Display; import android.view.InsetsFlags; import android.view.ViewDebug; import android.view.WindowInsetsController.Appearance; @@ -34,30 +34,32 @@ import androidx.annotation.Nullable; import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.view.AppearanceRegion; -import com.android.systemui.CoreStartable; -import com.android.systemui.Dumpable; -import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.settings.DisplayTracker; import com.android.systemui.statusbar.data.model.StatusBarAppearance; -import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore; +import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.Compile; -import com.android.systemui.util.kotlin.JavaAdapter; +import com.android.systemui.util.kotlin.JavaAdapterKt; + +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; + +import kotlin.coroutines.CoroutineContext; + +import kotlinx.coroutines.CoroutineScope; import java.io.PrintWriter; import java.util.ArrayList; -import javax.inject.Inject; - /** * Controls how light status bar flag applies to the icons. */ -@SysUISingleton -public class LightBarController implements - BatteryController.BatteryStateChangeCallback, Dumpable, CoreStartable { +public class LightBarControllerImpl implements + BatteryController.BatteryStateChangeCallback, LightBarController { private static final String TAG = "LightBarController"; private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG; @@ -65,11 +67,14 @@ public class LightBarController implements private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f; - private final JavaAdapter mJavaAdapter; + private final CoroutineScope mCoroutineScope; private final SysuiDarkIconDispatcher mStatusBarIconController; private final BatteryController mBatteryController; - private final StatusBarModeRepositoryStore mStatusBarModeRepository; - private BiometricUnlockController mBiometricUnlockController; + private final NavigationModeController mNavModeController; + private final DumpManager mDumpManager; + private final StatusBarModePerDisplayRepository mStatusBarModeRepository; + private final CoroutineContext mMainContext; + private final BiometricUnlockController mBiometricUnlockController; private LightBarTransitionsController mNavigationBarController; private @Appearance int mAppearance; @@ -119,47 +124,60 @@ public class LightBarController implements private String mLastNavigationBarAppearanceChangedLog; private StringBuilder mLogStringBuilder = null; - @Inject - public LightBarController( - Context ctx, - JavaAdapter javaAdapter, + private final String mDumpableName; + + private final NavigationModeController.ModeChangedListener mNavigationModeListener = + (mode) -> mNavigationMode = mode; + + @AssistedInject + public LightBarControllerImpl( + @Assisted int displayId, + @Assisted CoroutineScope coroutineScope, DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, - StatusBarModeRepositoryStore statusBarModeRepository, + @Assisted StatusBarModePerDisplayRepository statusBarModeRepository, DumpManager dumpManager, - DisplayTracker displayTracker) { - mJavaAdapter = javaAdapter; + @Main CoroutineContext mainContext, + BiometricUnlockController biometricUnlockController) { + mCoroutineScope = coroutineScope; mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher; mBatteryController = batteryController; - mBatteryController.addCallback(this); + mNavModeController = navModeController; + mDumpManager = dumpManager; mStatusBarModeRepository = statusBarModeRepository; - mNavigationMode = navModeController.addListener((mode) -> { - mNavigationMode = mode; - }); - - if (ctx.getDisplayId() == displayTracker.getDefaultDisplayId()) { - dumpManager.registerDumpable(getClass().getSimpleName(), this); - } + mMainContext = mainContext; + mBiometricUnlockController = biometricUnlockController; + String dumpableNameSuffix = + displayId == Display.DEFAULT_DISPLAY ? "" : String.valueOf(displayId); + mDumpableName = getClass().getSimpleName() + dumpableNameSuffix; } @Override public void start() { - mJavaAdapter.alwaysCollectFlow( - mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance(), + mDumpManager.registerCriticalDumpable(mDumpableName, this); + mBatteryController.addCallback(this); + mNavigationMode = mNavModeController.addListener(mNavigationModeListener); + JavaAdapterKt.collectFlow( + mCoroutineScope, + mMainContext, + mStatusBarModeRepository.getStatusBarAppearance(), this::onStatusBarAppearanceChanged); } + @Override + public void stop() { + mDumpManager.unregisterDumpable(mDumpableName); + mBatteryController.removeCallback(this); + mNavModeController.removeListener(mNavigationModeListener); + } + + @Override public void setNavigationBar(LightBarTransitionsController navigationBar) { mNavigationBarController = navigationBar; updateNavigation(); } - public void setBiometricUnlockController( - BiometricUnlockController biometricUnlockController) { - mBiometricUnlockController = biometricUnlockController; - } - private void onStatusBarAppearanceChanged(@Nullable StatusBarAppearance params) { if (params == null) { return; @@ -202,6 +220,7 @@ public class LightBarController implements mNavbarColorManagedByIme = navbarColorManagedByIme; } + @Override public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged, int navigationBarMode, boolean navbarColorManagedByIme) { int diff = appearance ^ mAppearance; @@ -244,6 +263,7 @@ public class LightBarController implements mNavbarColorManagedByIme = navbarColorManagedByIme; } + @Override public void onNavigationBarModeChanged(int newBarMode) { mHasLightNavigationBar = isLight(mAppearance, newBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS); } @@ -258,30 +278,28 @@ public class LightBarController implements mNavigationBarMode, mNavbarColorManagedByIme); } + @Override public void setQsCustomizing(boolean customizing) { if (mQsCustomizing == customizing) return; mQsCustomizing = customizing; reevaluate(); } - /** Set if Quick Settings is fully expanded, which affects notification scrim visibility */ + @Override public void setQsExpanded(boolean expanded) { if (mQsExpanded == expanded) return; mQsExpanded = expanded; reevaluate(); } - /** Set if Global Actions dialog is visible, which requires dark mode (light buttons) */ + @Override public void setGlobalActionsVisible(boolean visible) { if (mGlobalActionsVisible == visible) return; mGlobalActionsVisible = visible; reevaluate(); } - /** - * Controls the light status bar temporarily for back navigation. - * @param appearance the custmoized appearance. - */ + @Override public void customizeStatusBarAppearance(AppearanceRegion appearance) { if (appearance != null) { final ArrayList<AppearanceRegion> appearancesList = new ArrayList<>(); @@ -303,16 +321,14 @@ public class LightBarController implements } } - /** - * Sets whether the direct-reply is in use or not. - * @param directReplying {@code true} when the direct-reply is in-use. - */ + @Override public void setDirectReplying(boolean directReplying) { if (mDirectReplying == directReplying) return; mDirectReplying = directReplying; reevaluate(); } + @Override public void setScrimState(ScrimState scrimState, float scrimBehindAlpha, GradientColors scrimInFrontColor) { boolean bouncerVisibleLast = mBouncerVisible; @@ -368,9 +384,6 @@ public class LightBarController implements } private boolean animateChange() { - if (mBiometricUnlockController == null) { - return false; - } int unlockMode = mBiometricUnlockController.getMode(); return unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING && unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK; @@ -387,20 +400,17 @@ public class LightBarController implements } } - // If no one is light, all icons become white. if (lightBarBounds.isEmpty()) { - mStatusBarIconController.getTransitionsController().setIconsDark( - false, animateChange()); - } - - // If all stacks are light, all icons get dark. - else if (lightBarBounds.size() == numStacks) { + // If no one is light, all icons become white. + mStatusBarIconController + .getTransitionsController() + .setIconsDark(false, animateChange()); + } else if (lightBarBounds.size() == numStacks) { + // If all stacks are light, all icons get dark. mStatusBarIconController.setIconsDarkArea(null); mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange()); - } - - // Not the same for every stack, magic! - else { + } else { + // Not the same for every stack, magic! mStatusBarIconController.setIconsDarkArea(lightBarBounds); mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange()); } @@ -468,47 +478,15 @@ public class LightBarController implements } } - /** - * Injectable factory for creating a {@link LightBarController}. - */ - public static class Factory { - private final JavaAdapter mJavaAdapter; - private final DarkIconDispatcher mDarkIconDispatcher; - private final BatteryController mBatteryController; - private final NavigationModeController mNavModeController; - private final StatusBarModeRepositoryStore mStatusBarModeRepository; - private final DumpManager mDumpManager; - private final DisplayTracker mDisplayTracker; - - @Inject - public Factory( - JavaAdapter javaAdapter, - DarkIconDispatcher darkIconDispatcher, - BatteryController batteryController, - NavigationModeController navModeController, - StatusBarModeRepositoryStore statusBarModeRepository, - DumpManager dumpManager, - DisplayTracker displayTracker) { - mJavaAdapter = javaAdapter; - mDarkIconDispatcher = darkIconDispatcher; - mBatteryController = batteryController; - mNavModeController = navModeController; - mStatusBarModeRepository = statusBarModeRepository; - mDumpManager = dumpManager; - mDisplayTracker = displayTracker; - } + /** Injectable factory for creating a {@link LightBarControllerImpl}. */ + @AssistedFactory + @FunctionalInterface + public interface Factory { - /** Create an {@link LightBarController} */ - public LightBarController create(Context context) { - return new LightBarController( - context, - mJavaAdapter, - mDarkIconDispatcher, - mBatteryController, - mNavModeController, - mStatusBarModeRepository, - mDumpManager, - mDisplayTracker); - } + /** Creates a {@link LightBarControllerImpl}. */ + LightBarControllerImpl create( + int displayId, + CoroutineScope coroutineScope, + StatusBarModePerDisplayRepository statusBarModePerDisplayRepository); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 91c43ddf1ce4..176dd8de6cd4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -39,8 +39,8 @@ import com.android.systemui.Dependency; import com.android.systemui.Flags; import com.android.systemui.Gefingerpoken; import com.android.systemui.res.R; -import com.android.systemui.shade.LongPressGestureDetector; import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress; +import com.android.systemui.shade.StatusBarLongPressGestureDetector; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder; @@ -69,7 +69,7 @@ public class PhoneStatusBarView extends FrameLayout { private InsetsFetcher mInsetsFetcher; private int mDensity; private float mFontScale; - private LongPressGestureDetector mLongPressGestureDetector; + private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector; /** * Draw this many pixels into the left/right side of the cutout to optimally use the space @@ -81,9 +81,10 @@ public class PhoneStatusBarView extends FrameLayout { mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class); } - void setLongPressGestureDetector(LongPressGestureDetector longPressGestureDetector) { + void setLongPressGestureDetector( + StatusBarLongPressGestureDetector statusBarLongPressGestureDetector) { if (ShadeExpandsOnStatusBarLongPress.isEnabled()) { - mLongPressGestureDetector = longPressGestureDetector; + mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector; } } @@ -207,8 +208,9 @@ public class PhoneStatusBarView extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent event) { - if (ShadeExpandsOnStatusBarLongPress.isEnabled() && mLongPressGestureDetector != null) { - mLongPressGestureDetector.handleTouch(event); + if (ShadeExpandsOnStatusBarLongPress.isEnabled() + && mStatusBarLongPressGestureDetector != null) { + mStatusBarLongPressGestureDetector.handleTouch(event); } if (mTouchEventHandler == null) { Log.w( 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 c24f4327f471..424549453af3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -33,11 +33,11 @@ import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.ui.view.WindowRootView -import com.android.systemui.shade.LongPressGestureDetector import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress import com.android.systemui.shade.ShadeLogger import com.android.systemui.shade.ShadeViewController +import com.android.systemui.shade.StatusBarLongPressGestureDetector import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore @@ -68,7 +68,7 @@ private constructor( private val shadeController: ShadeController, private val shadeViewController: ShadeViewController, private val panelExpansionInteractor: PanelExpansionInteractor, - private val longPressGestureDetector: Provider<LongPressGestureDetector>, + private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>, private val windowRootView: Provider<WindowRootView>, private val shadeLogger: ShadeLogger, private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?, @@ -118,7 +118,7 @@ private constructor( addCursorSupportToIconContainers() if (ShadeExpandsOnStatusBarLongPress.isEnabled) { - mView.setLongPressGestureDetector(longPressGestureDetector.get()) + mView.setLongPressGestureDetector(statusBarLongPressGestureDetector.get()) } progressProvider?.setReadyToHandleTransition(true) @@ -335,7 +335,7 @@ private constructor( private val shadeController: ShadeController, private val shadeViewController: ShadeViewController, private val panelExpansionInteractor: PanelExpansionInteractor, - private val longPressGestureDetector: Provider<LongPressGestureDetector>, + private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>, private val windowRootView: Provider<WindowRootView>, private val shadeLogger: ShadeLogger, private val viewUtil: ViewUtil, @@ -360,7 +360,7 @@ private constructor( shadeController, shadeViewController, panelExpansionInteractor, - longPressGestureDetector, + statusBarLongPressGestureDetector, windowRootView, shadeLogger, statusBarMoveFromCenterAnimationController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt index 94de3510188c..ba878edc1132 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt @@ -103,8 +103,12 @@ interface StatusBarPhoneModule { fun statusBarInitializerImpl( implFactory: StatusBarInitializerImpl.Factory, statusBarWindowControllerStore: StatusBarWindowControllerStore, + statusBarModeRepositoryStore: StatusBarModeRepositoryStore, ): StatusBarInitializerImpl { - return implFactory.create(statusBarWindowControllerStore.defaultDisplay) + return implFactory.create( + statusBarWindowControllerStore.defaultDisplay, + statusBarModeRepositoryStore.defaultDisplay, + ) } @Provides diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt index 1d08f2ba9522..98eed848bab1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt @@ -37,6 +37,9 @@ interface DeviceBasedSatelliteRepository { /** Clients must observe this property, as device-based satellite is location-dependent */ val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean> + + /** When enabled, a satellite icon will display when all other connections are OOS */ + val isOpportunisticSatelliteIconEnabled: Boolean } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt index 58c30e018efd..de42b9229715 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt @@ -97,6 +97,9 @@ constructor( } .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl) + override val isOpportunisticSatelliteIconEnabled: Boolean + get() = activeRepo.value.isOpportunisticSatelliteIconEnabled + override val isSatelliteProvisioned: StateFlow<Boolean> = activeRepo .flatMapLatest { it.isSatelliteProvisioned } @@ -118,6 +121,6 @@ constructor( .stateIn( scope, SharingStarted.WhileSubscribed(), - realImpl.isSatelliteAllowedForCurrentLocation.value + realImpl.isSatelliteAllowedForCurrentLocation.value, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt index d557bbf306a6..755899f80928 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt @@ -16,15 +16,18 @@ package com.android.systemui.statusbar.pipeline.satellite.data.demo +import android.content.res.Resources +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow -import com.android.app.tracing.coroutines.launchTraced as launch /** A satellite repository that represents the latest satellite values sent via demo mode. */ @SysUISingleton @@ -33,9 +36,13 @@ class DemoDeviceBasedSatelliteRepository constructor( private val dataSource: DemoDeviceBasedSatelliteDataSource, @Application private val scope: CoroutineScope, + @Main resources: Resources, ) : DeviceBasedSatelliteRepository { private var demoCommandJob: Job? = null + override val isOpportunisticSatelliteIconEnabled = + resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon) + override val isSatelliteProvisioned = MutableStateFlow(true) override val connectionState = MutableStateFlow(SatelliteConnectionState.Unknown) override val signalStrength = MutableStateFlow(0) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt index 7686338fd9eb..a36ef56e57a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.satellite.data.prod +import android.content.res.Resources import android.os.OutcomeReceiver import android.telephony.TelephonyCallback import android.telephony.TelephonyManager @@ -27,14 +28,17 @@ import android.telephony.satellite.SatelliteModemStateCallback import android.telephony.satellite.SatelliteProvisionStateCallback import android.telephony.satellite.SatelliteSupportedStateCallback import androidx.annotation.VisibleForTesting +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.core.MessageInitializer import com.android.systemui.log.core.MessagePrinter +import com.android.systemui.res.R import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog import com.android.systemui.statusbar.pipeline.dagger.VerboseDeviceBasedSatelliteInputLog import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository @@ -66,7 +70,6 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext @@ -146,10 +149,14 @@ constructor( @DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer, @VerboseDeviceBasedSatelliteInputLog private val verboseLogBuffer: LogBuffer, private val systemClock: SystemClock, + @Main resources: Resources, ) : RealDeviceBasedSatelliteRepository { private val satelliteManager: SatelliteManager? + override val isOpportunisticSatelliteIconEnabled: Boolean = + resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon) + // Some calls into satellite manager will throw exceptions if it is not supported. // This is never expected to change after boot, but may need to be retried in some cases @get:VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt index f1a444f0158a..08a98c397d5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt @@ -53,6 +53,9 @@ constructor( @DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer, @DeviceBasedSatelliteTableLog private val tableLog: TableLogBuffer, ) { + /** Whether or not we should show the satellite icon when all connections are OOS */ + val isOpportunisticSatelliteIconEnabled = repo.isOpportunisticSatelliteIconEnabled + /** Must be observed by any UI showing Satellite iconography */ val isSatelliteAllowed = if (Flags.oemEnabledSatelliteFlag()) { @@ -93,12 +96,7 @@ constructor( flowOf(0) } .distinctUntilChanged() - .logDiffsForTable( - tableLog, - columnPrefix = "", - columnName = COL_LEVEL, - initialValue = 0, - ) + .logDiffsForTable(tableLog, columnPrefix = "", columnName = COL_LEVEL, initialValue = 0) .stateIn(scope, SharingStarted.WhileSubscribed(), 0) val isSatelliteProvisioned = repo.isSatelliteProvisioned @@ -132,10 +130,9 @@ constructor( /** When all connections are considered OOS, satellite connectivity is potentially valid */ val areAllConnectionsOutOfService = if (Flags.oemEnabledSatelliteFlag()) { - combine( - allConnectionsOos, - iconsInteractor.isDeviceInEmergencyCallsOnlyMode, - ) { connectionsOos, deviceEmergencyOnly -> + combine(allConnectionsOos, iconsInteractor.isDeviceInEmergencyCallsOnlyMode) { + connectionsOos, + deviceEmergencyOnly -> logBuffer.log( TAG, LogLevel.INFO, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt index 37f2f195ebf6..13ac321473f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt @@ -77,35 +77,38 @@ constructor( // This adds a 10 seconds delay before showing the icon private val shouldShowIconForOosAfterHysteresis: StateFlow<Boolean> = - interactor.areAllConnectionsOutOfService - .flatMapLatest { shouldShow -> - if (shouldShow) { - logBuffer.log( - TAG, - LogLevel.INFO, - { long1 = DELAY_DURATION.inWholeSeconds }, - { "Waiting $long1 seconds before showing the satellite icon" } + if (interactor.isOpportunisticSatelliteIconEnabled) { + interactor.areAllConnectionsOutOfService + .flatMapLatest { shouldShow -> + if (shouldShow) { + logBuffer.log( + TAG, + LogLevel.INFO, + { long1 = DELAY_DURATION.inWholeSeconds }, + { "Waiting $long1 seconds before showing the satellite icon" }, + ) + delay(DELAY_DURATION) + flowOf(true) + } else { + flowOf(false) + } + } + .distinctUntilChanged() + .logDiffsForTable( + tableLog, + columnPrefix = "vm", + columnName = COL_VISIBLE_FOR_OOS, + initialValue = false, ) - delay(DELAY_DURATION) - flowOf(true) - } else { - flowOf(false) - } + } else { + flowOf(false) } - .distinctUntilChanged() - .logDiffsForTable( - tableLog, - columnPrefix = "vm", - columnName = COL_VISIBLE_FOR_OOS, - initialValue = false, - ) .stateIn(scope, SharingStarted.WhileSubscribed(), false) private val canShowIcon = - combine( - interactor.isSatelliteAllowed, - interactor.isSatelliteProvisioned, - ) { allowed, provisioned -> + combine(interactor.isSatelliteAllowed, interactor.isSatelliteProvisioned) { + allowed, + provisioned -> allowed && provisioned } @@ -141,11 +144,10 @@ constructor( .stateIn(scope, SharingStarted.WhileSubscribed(), false) override val icon: StateFlow<Icon?> = - combine( - showIcon, - interactor.connectionState, - interactor.signalStrength, - ) { shouldShow, state, signalStrength -> + combine(showIcon, interactor.connectionState, interactor.signalStrength) { + shouldShow, + state, + signalStrength -> if (shouldShow) { SatelliteIconModel.fromConnectionState(state, signalStrength) } else { @@ -155,10 +157,7 @@ constructor( .stateIn(scope, SharingStarted.WhileSubscribed(), null) override val carrierText: StateFlow<String?> = - combine( - showIcon, - interactor.connectionState, - ) { shouldShow, connectionState -> + combine(showIcon, interactor.connectionState) { shouldShow, connectionState -> logBuffer.log( TAG, LogLevel.INFO, @@ -166,7 +165,7 @@ constructor( bool1 = shouldShow str1 = connectionState.name }, - { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" } + { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" }, ) if (shouldShow) { when (connectionState) { diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 9c8ef0421888..1c3fece1beb1 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -242,10 +242,16 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { } }; - private int getLatestWallpaperType(int userId) { - return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId) - > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId) - ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM; + private int getDefaultWallpaperColorsSource(int userId) { + if (com.android.systemui.shared.Flags.newCustomizationPickerUi()) { + // The wallpaper colors source is always the home wallpaper. + return WallpaperManager.FLAG_SYSTEM; + } else { + // The wallpaper colors source is based on the last set wallpaper. + return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId) + > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId) + ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM; + } } private boolean isSeedColorSet(JSONObject jsonObject, WallpaperColors newWallpaperColors) { @@ -279,9 +285,9 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { private void handleWallpaperColors(WallpaperColors wallpaperColors, int flags, int userId) { final int currentUser = mUserTracker.getUserId(); final boolean hadWallpaperColors = mCurrentColors.get(userId) != null; - int latestWallpaperType = getLatestWallpaperType(userId); - boolean eventForLatestWallpaper = (flags & latestWallpaperType) != 0; - if (eventForLatestWallpaper) { + int wallpaperColorsSource = getDefaultWallpaperColorsSource(userId); + boolean wallpaperColorsNeedUpdate = (flags & wallpaperColorsSource) != 0; + if (wallpaperColorsNeedUpdate) { mCurrentColors.put(userId, wallpaperColors); if (DEBUG) Log.d(TAG, "got new colors: " + wallpaperColors + " where: " + flags); } @@ -328,7 +334,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { boolean userChoseLockScreenColor = COLOR_SOURCE_LOCK.equals(wallpaperPickerColorSource); boolean preserveLockScreenColor = isDestinationHomeOnly && userChoseLockScreenColor; - if (!userChosePresetColor && !preserveLockScreenColor && eventForLatestWallpaper + if (!userChosePresetColor && !preserveLockScreenColor && wallpaperColorsNeedUpdate && !isSeedColorSet(jsonObject, wallpaperColors)) { mSkipSettingChange = true; if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has( @@ -494,7 +500,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { // Upon boot, make sure we have the most up to date colors Runnable updateColors = () -> { WallpaperColors systemColor = mWallpaperManager.getWallpaperColors( - getLatestWallpaperType(mUserTracker.getUserId())); + getDefaultWallpaperColorsSource(mUserTracker.getUserId())); Runnable applyColors = () -> { if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor); mCurrentColors.put(mUserTracker.getUserId(), systemColor); diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt index 3662c78efb16..163288b25b28 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt @@ -32,6 +32,7 @@ import android.os.UserHandle import android.os.UserManager import android.provider.Settings import android.util.Log +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.logging.UiEventLogger import com.android.internal.util.UserIcons import com.android.keyguard.KeyguardUpdateMonitor @@ -81,7 +82,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext @@ -109,7 +109,7 @@ constructor( private val guestUserInteractor: GuestUserInteractor, private val uiEventLogger: UiEventLogger, private val userRestrictionChecker: UserRestrictionChecker, - private val processWrapper: ProcessWrapper + private val processWrapper: ProcessWrapper, ) { /** * Defines interface for classes that can be notified when the state of users on the device is @@ -137,11 +137,10 @@ constructor( /** List of current on-device users to select from. */ val users: Flow<List<UserModel>> get() = - combine( + combine(userInfos, repository.selectedUserInfo, repository.userSwitcherSettings) { userInfos, - repository.selectedUserInfo, - repository.userSwitcherSettings, - ) { userInfos, selectedUserInfo, settings -> + selectedUserInfo, + settings -> toUserModels( userInfos = userInfos, selectedUserId = selectedUserInfo.id, @@ -157,7 +156,7 @@ constructor( toUserModel( userInfo = selectedUserInfo, selectedUserId = selectedUserId, - canSwitchUsers = canSwitchUsers(selectedUserId) + canSwitchUsers = canSwitchUsers(selectedUserId), ) } @@ -211,7 +210,7 @@ constructor( manager, repository, settings.isUserSwitcherEnabled, - canAccessUserSwitcher + canAccessUserSwitcher, ) if (canCreateUsers) { @@ -238,7 +237,7 @@ constructor( if ( UserActionsUtil.canManageUsers( repository, - settings.isUserSwitcherEnabled + settings.isUserSwitcherEnabled, ) ) { add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) @@ -248,18 +247,14 @@ constructor( .flowOn(backgroundDispatcher) val userRecords: StateFlow<ArrayList<UserRecord>> = - combine( + combine(userInfos, repository.selectedUserInfo, actions, repository.userSwitcherSettings) { userInfos, - repository.selectedUserInfo, - actions, - repository.userSwitcherSettings, - ) { userInfos, selectedUserInfo, actionModels, settings -> + selectedUserInfo, + actionModels, + settings -> ArrayList( userInfos.map { - toRecord( - userInfo = it, - selectedUserId = selectedUserInfo.id, - ) + toRecord(userInfo = it, selectedUserId = selectedUserInfo.id) } + actionModels.map { toRecord( @@ -298,7 +293,8 @@ constructor( val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting /** Whether to enable the user chip in the status bar */ - val isStatusBarUserChipEnabled: Boolean = repository.isStatusBarUserChipEnabled + val isStatusBarUserChipEnabled: Boolean + get() = repository.isStatusBarUserChipEnabled private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null) val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow() @@ -467,10 +463,8 @@ constructor( when (action) { UserActionModel.ENTER_GUEST_MODE -> { uiEventLogger.log(MultiUserActionsEvent.CREATE_GUEST_FROM_USER_SWITCHER) - guestUserInteractor.createAndSwitchTo( - this::showDialog, - this::dismissDialog, - ) { userId -> + guestUserInteractor.createAndSwitchTo(this::showDialog, this::dismissDialog) { + userId -> selectUser(userId, dialogShower) } } @@ -481,7 +475,7 @@ constructor( activityStarter.startActivity( CreateUserActivity.createIntentForStart( applicationContext, - keyguardInteractor.isKeyguardShowing() + keyguardInteractor.isKeyguardShowing(), ), /* dismissShade= */ true, /* animationController */ null, @@ -523,17 +517,14 @@ constructor( ) } - fun removeGuestUser( - @UserIdInt guestUserId: Int, - @UserIdInt targetUserId: Int, - ) { + fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int) { applicationScope.launch { guestUserInteractor.remove( guestUserId = guestUserId, targetUserId = targetUserId, ::showDialog, ::dismissDialog, - ::switchUser + ::switchUser, ) } } @@ -570,10 +561,7 @@ constructor( } } - private suspend fun toRecord( - userInfo: UserInfo, - selectedUserId: Int, - ): UserRecord { + private suspend fun toRecord(userInfo: UserInfo, selectedUserId: Int): UserRecord { return LegacyUserDataHelper.createRecord( context = applicationContext, manager = manager, @@ -595,10 +583,7 @@ constructor( actionType = action, isRestricted = isRestricted, isSwitchToEnabled = - canSwitchUsers( - selectedUserId = selectedUserId, - isAction = true, - ) && + canSwitchUsers(selectedUserId = selectedUserId, isAction = true) && // If the user is auto-created is must not be currently resetting. !(isGuestUserAutoCreated && isGuestUserResetting), userRestrictionChecker = userRestrictionChecker, @@ -623,10 +608,7 @@ constructor( } } - private suspend fun onBroadcastReceived( - intent: Intent, - previousUserInfo: UserInfo?, - ) { + private suspend fun onBroadcastReceived(intent: Intent, previousUserInfo: UserInfo?) { val shouldRefreshAllUsers = when (intent.action) { Intent.ACTION_LOCALE_CHANGED -> true @@ -645,10 +627,8 @@ constructor( Intent.ACTION_USER_INFO_CHANGED -> true Intent.ACTION_USER_UNLOCKED -> { // If we unlocked the system user, we should refresh all users. - intent.getIntExtra( - Intent.EXTRA_USER_HANDLE, - UserHandle.USER_NULL, - ) == UserHandle.USER_SYSTEM + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL) == + UserHandle.USER_SYSTEM } else -> true } @@ -668,20 +648,14 @@ constructor( // Disconnect from the old secondary user's service val secondaryUserId = repository.secondaryUserId if (secondaryUserId != UserHandle.USER_NULL) { - applicationContext.stopServiceAsUser( - intent, - UserHandle.of(secondaryUserId), - ) + applicationContext.stopServiceAsUser(intent, UserHandle.of(secondaryUserId)) repository.secondaryUserId = UserHandle.USER_NULL } // Connect to the new secondary user's service (purely to ensure that a persistent // SystemUI application is created for that user) if (userId != processWrapper.myUserHandle().identifier && !processWrapper.isSystemUser) { - applicationContext.startServiceAsUser( - intent, - UserHandle.of(userId), - ) + applicationContext.startServiceAsUser(intent, UserHandle.of(userId)) repository.secondaryUserId = userId } } @@ -732,7 +706,7 @@ constructor( private suspend fun toUserModel( userInfo: UserInfo, selectedUserId: Int, - canSwitchUsers: Boolean + canSwitchUsers: Boolean, ): UserModel { val userId = userInfo.id val isSelected = userId == selectedUserId @@ -740,11 +714,7 @@ constructor( UserModel( id = userId, name = Text.Loaded(userInfo.name), - image = - getUserImage( - isGuest = true, - userId = userId, - ), + image = getUserImage(isGuest = true, userId = userId), isSelected = isSelected, isSelectable = canSwitchUsers, isGuest = true, @@ -753,11 +723,7 @@ constructor( UserModel( id = userId, name = Text.Loaded(userInfo.name), - image = - getUserImage( - isGuest = false, - userId = userId, - ), + image = getUserImage(isGuest = false, userId = userId), isSelected = isSelected, isSelectable = canSwitchUsers || isSelected, isGuest = false, @@ -765,10 +731,7 @@ constructor( } } - private suspend fun canSwitchUsers( - selectedUserId: Int, - isAction: Boolean = false, - ): Boolean { + private suspend fun canSwitchUsers(selectedUserId: Int, isAction: Boolean = false): Boolean { val isHeadlessSystemUserMode = withContext(backgroundDispatcher) { headlessSystemUserMode.isHeadlessSystemUserMode() } // Whether menu item should be active. True if item is a user or if any user has @@ -785,7 +748,7 @@ constructor( .getUsers( /* excludePartial= */ true, /* excludeDying= */ true, - /* excludePreCreated= */ true + /* excludePreCreated= */ true, ) .any { user -> user.id != UserHandle.USER_SYSTEM && @@ -794,10 +757,7 @@ constructor( } @SuppressLint("UseCompatLoadingForDrawables") - private suspend fun getUserImage( - isGuest: Boolean, - userId: Int, - ): Drawable { + private suspend fun getUserImage(isGuest: Boolean, userId: Int): Drawable { if (isGuest) { return checkNotNull( applicationContext.getDrawable(com.android.settingslib.R.drawable.ic_account_circle) @@ -823,13 +783,13 @@ constructor( return UserIcons.getDefaultUserIcon( applicationContext.resources, userId, - /* light= */ false + /* light= */ false, ) } private fun canCreateGuestUser( settings: UserSwitcherSettingsModel, - canAccessUserSwitcher: Boolean + canAccessUserSwitcher: Boolean, ): Boolean { return guestUserInteractor.isGuestUserAutoCreated || UserActionsUtil.canCreateGuest( diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt index 2c425b199b4e..53c2d888922b 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt @@ -30,11 +30,10 @@ import kotlinx.coroutines.flow.mapLatest @OptIn(ExperimentalCoroutinesApi::class) class StatusBarUserChipViewModel @Inject -constructor( - interactor: UserSwitcherInteractor, -) { +constructor(private val interactor: UserSwitcherInteractor) { /** Whether the status bar chip ui should be available */ - val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled + val chipEnabled: Boolean + get() = interactor.isStatusBarUserChipEnabled /** Whether or not the chip should be showing, based on the number of users */ val isChipVisible: Flow<Boolean> = diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt index 315912406b6d..63a5b3f1e6f6 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt @@ -20,6 +20,7 @@ import android.view.View import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope import androidx.lifecycle.repeatOnLifecycle +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.lifecycle.repeatWhenAttached @@ -35,7 +36,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn -import com.android.app.tracing.coroutines.launchTraced as launch +import kotlinx.coroutines.plus /** A class allowing Java classes to collect on Kotlin flows. */ @SysUISingleton @@ -102,6 +103,22 @@ fun <T> collectFlow( } } +/** + * Collect information for the given [flow], calling [consumer] for each emitted event on the + * specified [collectContext]. + * + * Collection will continue until the given [scope] is cancelled. + */ +@JvmOverloads +fun <T> collectFlow( + scope: CoroutineScope, + collectContext: CoroutineContext = scope.coroutineContext, + flow: Flow<T>, + consumer: Consumer<T>, +): Job { + return scope.plus(collectContext).launch { flow.collect { consumer.accept(it) } } +} + fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> { return combine(flow1, flow2, bifunction) } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java index d509b2da482e..f36c335e0f44 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java @@ -16,9 +16,6 @@ package com.android.systemui.util.settings; -import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository; -import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl; - import dagger.Binds; import dagger.Module; @@ -39,9 +36,4 @@ public interface SettingsUtilModule { /** Bind GlobalSettingsImpl to GlobalSettings. */ @Binds GlobalSettings bindsGlobalSettings(GlobalSettingsImpl impl); - - /** Bind UserAwareSecureSettingsRepositoryImpl to UserAwareSecureSettingsRepository. */ - @Binds - UserAwareSecureSettingsRepository bindsUserAwareSecureSettingsRepository( - UserAwareSecureSettingsRepositoryImpl impl); } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt index d3e50803b5d5..71335ec84c86 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt @@ -19,52 +19,25 @@ package com.android.systemui.util.settings.repository import android.provider.Settings import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.settings.SecureSettings -import com.android.systemui.util.settings.SettingsProxy -import com.android.systemui.util.settings.SettingsProxyExt.observerFlow -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineDispatcher /** * Repository for observing values of [Settings.Secure] for the currently active user. That means * when user is switched and the new user has different value, flow will emit new value. */ -interface UserAwareSecureSettingsRepository { - - /** - * Emits boolean value of the setting for active user. Also emits starting value when - * subscribed. - * See: [SettingsProxy.getBool]. - */ - fun boolSettingForActiveUser(name: String, defaultValue: Boolean = false): Flow<Boolean> -} - @SysUISingleton -@OptIn(ExperimentalCoroutinesApi::class) -class UserAwareSecureSettingsRepositoryImpl @Inject constructor( - private val secureSettings: SecureSettings, - private val userRepository: UserRepository, - @Background private val backgroundDispatcher: CoroutineDispatcher, -) : UserAwareSecureSettingsRepository { - - override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> = - userRepository.selectedUserInfo - .flatMapLatest { userInfo -> settingObserver(name, defaultValue, userInfo.id) } - .distinctUntilChanged() - .flowOn(backgroundDispatcher) - - private fun settingObserver(name: String, defaultValue: Boolean, userId: Int): Flow<Boolean> { - return secureSettings - .observerFlow(userId, name) - .onStart { emit(Unit) } - .map { secureSettings.getBoolForUser(name, defaultValue, userId) } - } -}
\ No newline at end of file +class UserAwareSecureSettingsRepository +@Inject +constructor( + secureSettings: SecureSettings, + userRepository: UserRepository, + @Background backgroundDispatcher: CoroutineDispatcher, + @Background bgContext: CoroutineContext, +) : + UserAwareSettingsRepository(secureSettings, userRepository, backgroundDispatcher, bgContext), + SecureSettingsRepository diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt new file mode 100644 index 000000000000..a31b8d943a1b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt @@ -0,0 +1,94 @@ +/* + * 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.util.settings.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import com.android.systemui.util.settings.UserSettingsProxy +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.withContext + +/** + * Repository for observing values of a [UserSettingsProxy], for the currently active user. That + * means that when user is switched and the new user has a different value, the flow will emit the + * new value. + */ +@SysUISingleton +@OptIn(ExperimentalCoroutinesApi::class) +abstract class UserAwareSettingsRepository( + private val userSettings: UserSettingsProxy, + private val userRepository: UserRepository, + @Background private val backgroundDispatcher: CoroutineDispatcher, + @Background private val bgContext: CoroutineContext, +) { + + fun boolSetting(name: String, defaultValue: Boolean): Flow<Boolean> = + userRepository.selectedUserInfo + .flatMapLatest { userInfo -> + settingObserver(name, userInfo.id) { + userSettings.getBoolForUser(name, defaultValue, userInfo.id) + } + } + .distinctUntilChanged() + .flowOn(backgroundDispatcher) + + fun intSetting(name: String, defaultValue: Int): Flow<Int> { + return userRepository.selectedUserInfo + .flatMapLatest { userInfo -> + settingObserver(name, userInfo.id) { + userSettings.getIntForUser(name, defaultValue, userInfo.id) + } + } + .distinctUntilChanged() + .flowOn(backgroundDispatcher) + } + + private fun <T> settingObserver(name: String, userId: Int, settingsReader: () -> T): Flow<T> { + return userSettings + .observerFlow(userId, name) + .onStart { emit(Unit) } + .map { settingsReader.invoke() } + } + + suspend fun setInt(name: String, value: Int) { + withContext(bgContext) { + userSettings.putIntForUser(name, value, userRepository.getSelectedUserInfo().id) + } + } + + suspend fun getInt(name: String, defaultValue: Int): Int { + return withContext(bgContext) { + userSettings.getIntForUser(name, defaultValue, userRepository.getSelectedUserInfo().id) + } + } + + suspend fun getString(name: String): String? { + return withContext(bgContext) { + userSettings.getStringForUser(name, userRepository.getSelectedUserInfo().id) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt index 02ce74a94de6..8b1fca551d51 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt @@ -14,25 +14,30 @@ * limitations under the License. */ -package com.android.systemui.settings +package com.android.systemui.util.settings.repository -import android.content.ContentResolver +import android.provider.Settings import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository -import com.android.systemui.shared.settings.data.repository.SystemSettingsRepositoryImpl -import dagger.Module -import dagger.Provides +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.settings.SystemSettings +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineDispatcher -@Module -object SystemSettingsRepositoryModule { - @JvmStatic - @Provides - @SysUISingleton - fun provideSystemSettingsRepository( - contentResolver: ContentResolver, - @Background backgroundDispatcher: CoroutineDispatcher, - ): SystemSettingsRepository = - SystemSettingsRepositoryImpl(contentResolver, backgroundDispatcher) -} +/** + * Repository for observing values of [Settings.Secure] for the currently active user. That means + * when user is switched and the new user has different value, flow will emit new value. + */ +@SysUISingleton +class UserAwareSystemSettingsRepository +@Inject +constructor( + systemSettings: SystemSettings, + userRepository: UserRepository, + @Background backgroundDispatcher: CoroutineDispatcher, + @Background bgContext: CoroutineContext, +) : + UserAwareSettingsRepository(systemSettings, userRepository, backgroundDispatcher, bgContext), + SystemSettingsRepository diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt index 617aaa71d2d3..d5b8597e36ed 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt @@ -53,7 +53,9 @@ interface AudioModule { fun provideAudioManagerIntentsReceiver( @Application context: Context, @Application coroutineScope: CoroutineScope, - ): AudioManagerEventsReceiver = AudioManagerEventsReceiverImpl(context, coroutineScope) + @Background coroutineContext: CoroutineContext, + ): AudioManagerEventsReceiver = + AudioManagerEventsReceiverImpl(context, coroutineScope, coroutineContext) @Provides @SysUISingleton @@ -82,7 +84,7 @@ interface AudioModule { localBluetoothManager: LocalBluetoothManager?, @Application coroutineScope: CoroutineScope, @Background coroutineContext: CoroutineContext, - volumeLogger: VolumeLogger + volumeLogger: VolumeLogger, ): AudioSharingRepository = if (Flags.enableLeAudioSharing() && localBluetoothManager != null) { AudioSharingRepositoryImpl( @@ -90,7 +92,7 @@ interface AudioModule { localBluetoothManager, coroutineScope, coroutineContext, - volumeLogger + volumeLogger, ) } else { AudioSharingRepositoryEmptyImpl() @@ -111,8 +113,7 @@ interface AudioModule { @Provides @SysUISingleton - fun provideAudioSystemRepository( - @Application context: Context, - ): AudioSystemRepository = AudioSystemRepositoryImpl(context) + fun provideAudioSystemRepository(@Application context: Context): AudioSystemRepository = + AudioSystemRepositoryImpl(context) } } 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/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt index dacd6c78b034..b9f47d7ad110 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt @@ -22,15 +22,17 @@ import android.media.session.MediaSession import android.media.session.PlaybackState import android.os.Bundle import android.os.Handler +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import com.android.app.tracing.coroutines.launchTraced as launch +import kotlinx.coroutines.flow.flowOn interface MediaControllerInteractor { @@ -43,14 +45,16 @@ class MediaControllerInteractorImpl @Inject constructor( @Background private val backgroundHandler: Handler, + @Background private val backgroundCoroutineContext: CoroutineContext, ) : MediaControllerInteractor { override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> { return conflatedCallbackFlow { - val callback = MediaControllerCallbackProducer(this) - mediaController.registerCallback(callback, backgroundHandler) - awaitClose { mediaController.unregisterCallback(callback) } - } + val callback = MediaControllerCallbackProducer(this) + mediaController.registerCallback(callback, backgroundHandler) + awaitClose { mediaController.unregisterCallback(callback) } + } + .flowOn(backgroundCoroutineContext) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt index aa07cfd26bdb..b3848a6d7817 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt @@ -20,10 +20,12 @@ import android.content.pm.PackageManager import android.media.VolumeProvider import android.media.session.MediaController import android.util.Log +import androidx.annotation.WorkerThread import com.android.settingslib.media.MediaDevice import com.android.settingslib.volume.data.repository.LocalMediaRepository import com.android.settingslib.volume.data.repository.MediaControllerRepository import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.concurrency.Execution import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession @@ -62,6 +64,7 @@ constructor( @Background private val backgroundCoroutineContext: CoroutineContext, mediaControllerRepository: MediaControllerRepository, private val mediaControllerInteractor: MediaControllerInteractor, + private val execution: Execution, ) { private val activeMediaControllers: Flow<MediaControllers> = @@ -82,9 +85,10 @@ constructor( .map { MediaDeviceSessions( local = it.local?.mediaDeviceSession(), - remote = it.remote?.mediaDeviceSession() + remote = it.remote?.mediaDeviceSession(), ) } + .flowOn(backgroundCoroutineContext) .stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSessions(null, null)) /** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */ @@ -115,55 +119,43 @@ constructor( val currentConnectedDevice: Flow<MediaDevice?> = localMediaRepository.flatMapLatest { it.currentConnectedDevice }.distinctUntilChanged() - private suspend fun getApplicationLabel(packageName: String): CharSequence? { - return try { - withContext(backgroundCoroutineContext) { - val appInfo = - packageManager.getApplicationInfo( - packageName, - PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER - ) - appInfo.loadLabel(packageManager) - } - } catch (e: PackageManager.NameNotFoundException) { - Log.e(TAG, "Unable to find info for package: $packageName") - null - } - } - /** Finds local and remote media controllers. */ - private fun getMediaControllers( - controllers: Collection<MediaController>, - ): MediaControllers { - var localController: MediaController? = null - var remoteController: MediaController? = null - val remoteMediaSessions: MutableSet<String> = mutableSetOf() - for (controller in controllers) { - val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue - when (playbackInfo.playbackType) { - MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> { - // MediaController can't be local if there is a remote one for the same package - if (localController?.packageName.equals(controller.packageName)) { - localController = null + private suspend fun getMediaControllers( + controllers: Collection<MediaController> + ): MediaControllers = + withContext(backgroundCoroutineContext) { + var localController: MediaController? = null + var remoteController: MediaController? = null + val remoteMediaSessions: MutableSet<String> = mutableSetOf() + for (controller in controllers) { + val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue + when (playbackInfo.playbackType) { + MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> { + // MediaController can't be local if there is a remote one for the same + // package + if (localController?.packageName.equals(controller.packageName)) { + localController = null + } + if (!remoteMediaSessions.contains(controller.packageName)) { + remoteMediaSessions.add(controller.packageName) + remoteController = chooseController(remoteController, controller) + } } - if (!remoteMediaSessions.contains(controller.packageName)) { - remoteMediaSessions.add(controller.packageName) - remoteController = chooseController(remoteController, controller) + MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> { + if (controller.packageName in remoteMediaSessions) continue + localController = chooseController(localController, controller) } } - MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> { - if (controller.packageName in remoteMediaSessions) continue - localController = chooseController(localController, controller) - } } + MediaControllers(local = localController, remote = remoteController) } - return MediaControllers(local = localController, remote = remoteController) - } + @WorkerThread private fun chooseController( currentController: MediaController?, newController: MediaController, ): MediaController { + require(!execution.isMainThread()) if (currentController == null) { return newController } @@ -175,12 +167,26 @@ constructor( return currentController } - private suspend fun MediaController.mediaDeviceSession(): MediaDeviceSession? { + @WorkerThread + private fun MediaController.mediaDeviceSession(): MediaDeviceSession? { + require(!execution.isMainThread()) + val applicationLabel = + try { + packageManager + .getApplicationInfo( + packageName, + PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER, + ) + .loadLabel(packageManager) + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Unable to find info for package: $packageName") + null + } ?: return null return MediaDeviceSession( packageName = packageName, sessionToken = sessionToken, canAdjustVolume = playbackInfo.volumeControl != VolumeProvider.VOLUME_CONTROL_FIXED, - appLabel = getApplicationLabel(packageName) ?: return null + appLabel = applicationLabel, ) } @@ -195,10 +201,7 @@ constructor( .onStart { emit(this@stateChanges) } } - private data class MediaControllers( - val local: MediaController?, - val remote: MediaController?, - ) + private data class MediaControllers(val local: MediaController?, val remote: MediaController?) private companion object { const val TAG = "MediaOutputInteractor" 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_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json index 18eedd450751..825190ba7a32 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_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_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json index 18eedd450751..63c263175122 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_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/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java index a95735e56f04..82cfab6fde06 100644 --- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java +++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java @@ -56,7 +56,6 @@ import java.util.Collections; @RunWith(AndroidTestingRunner.class) @SmallTest public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase { - private static final String TAG = "AAA++VerifyTest"; private static final Class[] BASE_CLS_TO_INCLUDE = { @@ -149,6 +148,9 @@ public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestC */ private boolean isTestClass(Class<?> loadedClass) { try { + if (loadedClass.getAnnotation(SkipSysuiVerification.class) != null) { + return false; + } if (Modifier.isAbstract(loadedClass.getModifiers())) { logDebug(String.format("Skipping abstract class %s: not a test", loadedClass.getName())); 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/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index e1b8a1d9971c..91f9cce5b69b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -212,6 +212,20 @@ open class AuthContainerViewTest : SysuiTestCase() { } @Test + fun testDimissOnLock() { + val container = initializeFingerprintContainer(addToView = true) + assertThat(container.parent).isNotNull() + val root = container.rootView + + // Simulate sleep/lock invocation + container.onStartedGoingToSleep() + waitForIdleSync() + + assertThat(container.parent).isNull() + assertThat(root.isAttachedToWindow).isFalse() + } + + @Test fun testCredentialPasswordDismissesOnBack() { val container = initializeCredentialPasswordContainer(addToView = true) assertThat(container.parent).isNotNull() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt index 9ace8e981077..387cc084f9cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt @@ -42,6 +42,7 @@ import com.android.systemui.motion.createSysUiComposeMotionTestRule import com.android.systemui.res.R import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.testKosmos +import kotlin.time.Duration.Companion.seconds import org.junit.After import org.junit.Before import org.junit.Rule @@ -109,7 +110,7 @@ class BouncerContentTest : SysuiTestCase() { @Test fun doubleClick_swapSide() = - motionTestRule.runTest { + motionTestRule.runTest(timeout = 30.seconds) { val motion = recordMotion( content = { BouncerContentUnderTest() }, diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt index 088bb02512b5..768f1dc8b78c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.haptics.msdl.bouncerHapticPlayer import com.android.systemui.lifecycle.activateIn import com.android.systemui.motion.createSysUiComposeMotionTestRule import com.android.systemui.testKosmos +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.takeWhile @@ -71,7 +72,7 @@ class PatternBouncerTest : SysuiTestCase() { @Test fun entryAnimation() = - motionTestRule.runTest { + motionTestRule.runTest(timeout = 30.seconds) { val motion = recordMotion( content = { play -> if (play) PatternBouncerUnderTest() }, @@ -89,7 +90,7 @@ class PatternBouncerTest : SysuiTestCase() { @Test fun animateFailure() = - motionTestRule.runTest { + motionTestRule.runTest(timeout = 30.seconds) { val failureAnimationMotionControl = MotionControl( delayReadyToPlay = { 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/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt index 7709a65712a1..0ab4cd05fea7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt @@ -21,7 +21,6 @@ import android.graphics.Insets import android.graphics.Rect import android.os.UserHandle import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.view.Display.DEFAULT_DISPLAY import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD @@ -76,9 +75,8 @@ class PolicyRequestProcessorTest { /** Tests applying CaptureParameters with 'IsolatedTask' CaptureType */ @Test - @EnableFlags(Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE) fun testProcess_newPolicy_isolatedTask() = runTest { - val taskImage = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + val taskImage = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888) /* Create a policy request processor with no capture policies */ val requestProcessor = @@ -96,9 +94,15 @@ class PolicyRequestProcessorTest { requestProcessor.modify( screenshotRequest, CaptureParameters( - IsolatedTask(taskId = TASK_ID, taskBounds = null), - ComponentName.unflattenFromString(FILES), - UserHandle.of(WORK), + type = IsolatedTask(taskId = 100, taskBounds = Rect(0, 100, 200, 200)), + contentTask = + TaskReference( + taskId = 1001, + component = ComponentName.unflattenFromString(FILES)!!, + owner = UserHandle.CURRENT, + bounds = Rect(100, 100, 200, 200), + ), + owner = UserHandle.of(WORK), ), ) @@ -112,14 +116,13 @@ class PolicyRequestProcessorTest { .that(result.topComponent) .isEqualTo(ComponentName.unflattenFromString(FILES)) - assertWithMessage("Task ID").that(result.taskId).isEqualTo(TASK_ID) + assertWithMessage("Task ID").that(result.taskId).isEqualTo(1001) } /** Tests applying CaptureParameters with 'FullScreen' CaptureType */ @Test - @EnableFlags(Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE) fun testProcess_newPolicy_fullScreen() = runTest { - val screenImage = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + val screenImage = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888) /* Create a policy request processor with no capture policies */ val requestProcessor = @@ -136,7 +139,17 @@ class PolicyRequestProcessorTest { val result = requestProcessor.modify( screenshotRequest, - CaptureParameters(FullScreen(displayId = 0), defaultComponent, defaultOwner), + CaptureParameters( + type = FullScreen(displayId = 0), + contentTask = + TaskReference( + taskId = 1234, + component = defaultComponent, + owner = UserHandle.CURRENT, + bounds = Rect(1, 2, 3, 4), + ), + owner = defaultOwner, + ), ) assertWithMessage("The result bitmap").that(result.bitmap).isSameInstanceAs(screenImage) @@ -149,7 +162,11 @@ class PolicyRequestProcessorTest { .that(result.topComponent) .isEqualTo(defaultComponent) - assertWithMessage("Task ID").that(result.taskId).isEqualTo(-1) + assertWithMessage("The bounds of the screenshot") + .that(result.originalScreenBounds) + .isEqualTo(Rect(0, 0, 100, 100)) + + assertWithMessage("Task ID").that(result.taskId).isEqualTo(1234) } /** Tests behavior when no policies are applied */ @@ -230,7 +247,7 @@ class PolicyRequestProcessorTest { policy = "", reason = "", parameters = - CaptureParameters( + LegacyCaptureParameters( IsolatedTask(taskId = 0, taskBounds = null), null, UserHandle.CURRENT, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index a8929a63a812..f870200c2b00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.notification.logging; -import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; - import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -59,8 +57,6 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; -import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository; -import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.logging.nano.Notifications; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor; @@ -118,11 +114,6 @@ public class NotificationLoggerTest extends SysuiTestCase { private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); private final PowerInteractor mPowerInteractor = PowerInteractorFactory.create().getPowerInteractor(); - private final ActiveNotificationListRepository mActiveNotificationListRepository = - new ActiveNotificationListRepository(); - private final ActiveNotificationsInteractor mActiveNotificationsInteractor = - new ActiveNotificationsInteractor(mActiveNotificationListRepository, - StandardTestDispatcher(null, null)); private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); @@ -137,7 +128,7 @@ public class NotificationLoggerTest extends SysuiTestCase { mKeyguardRepository, mHeadsUpManager, mPowerInteractor, - mActiveNotificationsInteractor, + mKosmos.getActiveNotificationsInteractor(), () -> mKosmos.getSceneInteractor()); mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 625963f5ec7a..0427011c06f6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -28,8 +28,6 @@ import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanki import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; -import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; @@ -87,8 +85,6 @@ 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.data.repository.ActiveNotificationListRepository; -import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; 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; @@ -159,12 +155,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private UserManager mUserManager; - private final ActiveNotificationListRepository mActiveNotificationListRepository = - new ActiveNotificationListRepository(); - private final ActiveNotificationsInteractor mActiveNotificationsInteractor = - new ActiveNotificationsInteractor(mActiveNotificationListRepository, - StandardTestDispatcher(null, null)); - private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; @Before @@ -179,7 +169,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { new FakeKeyguardRepository(), mHeadsUpManager, PowerInteractorFactory.create().getPowerInteractor(), - mActiveNotificationsInteractor, + mKosmos.getActiveNotificationsInteractor(), () -> mKosmos.getSceneInteractor() ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 05ec85a4cb4f..d2350bc82667 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.stack; import static android.view.View.GONE; import static android.view.WindowInsets.Type.ime; +import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL; @@ -54,6 +55,7 @@ import android.graphics.Rect; import android.os.SystemClock; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper; import android.testing.TestableResources; import android.util.MathUtils; @@ -64,13 +66,13 @@ import android.view.WindowInsets; import android.view.WindowInsetsAnimation; import android.widget.TextView; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.ExpandHelper; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.BrokenWithSceneContainer; import com.android.systemui.flags.DisableSceneContainer; import com.android.systemui.flags.EnableSceneContainer; import com.android.systemui.flags.FakeFeatureFlags; @@ -118,16 +120,25 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + /** * Tests for {@link NotificationStackScrollLayout}. */ @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper public class NotificationStackScrollLayoutTest extends SysuiTestCase { + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return parameterizeSceneContainerFlag(); + } + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private NotificationStackScrollLayout mStackScroller; // Normally test this private NotificationStackScrollLayout mStackScrollerInternal; // See explanation below @@ -154,6 +165,11 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; @Mock private AvalancheController mAvalancheController; + public NotificationStackScrollLayoutTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); @@ -353,6 +369,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void updateStackEndHeightAndStackHeight_onlyUpdatesStackHeightDuringSwipeUp() { final float expansionFraction = 0.5f; mAmbientState.setStatusBarState(StatusBarState.KEYGUARD); @@ -366,6 +383,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void setPanelFlinging_updatesStackEndHeightOnlyOnFinish() { final float expansionFraction = 0.5f; mAmbientState.setStatusBarState(StatusBarState.KEYGUARD); @@ -1431,6 +1449,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test @EnableFlags(NotificationThrottleHun.FLAG_NAME) + @BrokenWithSceneContainer(bugId = 332732878) // because NSSL#mAnimationsEnabled is always true public void testGenerateHeadsUpAnimation_isSeenInShade_noAnimation() { // GIVEN NSSL is ready for HUN animations Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); 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 f472fd1019eb..7d019bf1be92 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 @@ -155,6 +155,7 @@ import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeControllerImpl; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.ShadeLogger; +import com.android.systemui.shade.StatusBarLongPressGestureDetector; import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyboardShortcutListSearch; @@ -174,8 +175,8 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.core.StatusBarConnectedDisplays; import com.android.systemui.statusbar.core.StatusBarInitializerImpl; -import com.android.systemui.statusbar.core.StatusBarOrchestrator; import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository; +import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; @@ -371,7 +372,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory; @Mock private NotificationSettingsInteractor mNotificationSettingsInteractor; @Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager; - @Mock private StatusBarOrchestrator mStatusBarOrchestrator; + @Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings(); @@ -387,6 +388,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor = mKosmos.getBrightnessMirrorShowingInteractor(); + + private final StatusBarModePerDisplayRepository mStatusBarModePerDisplayRepository = + mKosmos.getStatusBarModePerDisplayRepository(); private ScrimController mScrimController; @Before @@ -537,6 +541,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mAutoHideController, new StatusBarInitializerImpl( mStatusBarWindowController, + mStatusBarModePerDisplayRepository, mCollapsedStatusBarFragmentProvider, mock(StatusBarRootFactory.class), mock(HomeStatusBarComponent.Factory.class), @@ -602,6 +607,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mShadeController, mWindowRootViewVisibilityInteractor, mStatusBarKeyguardViewManager, + () -> mStatusBarLongPressGestureDetector, mViewMediatorCallback, mInitController, new Handler(TestableLooper.get(this).getLooper()), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index 638f195df00c..69efa87a9cac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -40,14 +40,13 @@ import com.android.systemui.flags.Flags import com.android.systemui.plugins.fakeDarkIconDispatcher import com.android.systemui.res.R import com.android.systemui.scene.ui.view.WindowRootView -import com.android.systemui.shade.LongPressGestureDetector import com.android.systemui.shade.ShadeControllerImpl import com.android.systemui.shade.ShadeLogger import com.android.systemui.shade.ShadeViewController +import com.android.systemui.shade.StatusBarLongPressGestureDetector import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore -import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore import com.android.systemui.statusbar.policy.Clock import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.window.StatusBarWindowStateController @@ -98,7 +97,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Mock private lateinit var windowRootView: Provider<WindowRootView> @Mock private lateinit var shadeLogger: ShadeLogger @Mock private lateinit var viewUtil: ViewUtil - @Mock private lateinit var longPressGestureDetector: LongPressGestureDetector + @Mock private lateinit var mStatusBarLongPressGestureDetector: StatusBarLongPressGestureDetector private lateinit var statusBarWindowStateController: StatusBarWindowStateController private lateinit var view: PhoneStatusBarView @@ -395,7 +394,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { shadeControllerImpl, shadeViewController, panelExpansionInteractor, - { longPressGestureDetector }, + { mStatusBarLongPressGestureDetector }, windowRootView, shadeLogger, viewUtil, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt index 4d293b98c165..6326e73aec67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt @@ -102,6 +102,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { logBuffer = FakeLogBuffer.Factory.create(), verboseLogBuffer = FakeLogBuffer.Factory.create(), systemClock, + context.resources, ) val connectionState by collectLastValue(underTest.connectionState) @@ -267,11 +268,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { fun satelliteProvisioned_notSupported_defaultFalse() = testScope.runTest { // GIVEN satellite is not supported - setUpRepo( - uptime = MIN_UPTIME, - satMan = satelliteManager, - satelliteSupported = false, - ) + setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false) assertThat(underTest.isSatelliteProvisioned.value).isFalse() } @@ -280,11 +277,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { fun satelliteProvisioned_supported_defaultFalse() = testScope.runTest { // GIVEN satellite is supported - setUpRepo( - uptime = MIN_UPTIME, - satMan = satelliteManager, - satelliteSupported = true, - ) + setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true) // THEN default provisioned state is false assertThat(underTest.isSatelliteProvisioned.value).isFalse() @@ -323,6 +316,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { logBuffer = FakeLogBuffer.Factory.create(), verboseLogBuffer = FakeLogBuffer.Factory.create(), systemClock, + context.resources, ) // WHEN we try to check for provisioned status @@ -361,6 +355,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { logBuffer = FakeLogBuffer.Factory.create(), verboseLogBuffer = FakeLogBuffer.Factory.create(), systemClock, + context.resources, ) // WHEN we try to check for provisioned status @@ -445,11 +440,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { fun satelliteProvisioned_supported_tracksCallback_reRegistersOnCrash() = testScope.runTest { // GIVEN satellite is supported - setUpRepo( - uptime = MIN_UPTIME, - satMan = satelliteManager, - satelliteSupported = true, - ) + setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true) val provisioned by collectLastValue(underTest.isSatelliteProvisioned) @@ -487,11 +478,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { fun satelliteNotSupported_listenersAreNotRegistered() = testScope.runTest { // GIVEN satellite is not supported - setUpRepo( - uptime = MIN_UPTIME, - satMan = satelliteManager, - satelliteSupported = false, - ) + setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false) // WHEN data is requested from the repo val connectionState by collectLastValue(underTest.connectionState) @@ -517,11 +504,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { fun satelliteNotSupported_registersCallbackForStateChanges() = testScope.runTest { // GIVEN satellite is not supported - setUpRepo( - uptime = MIN_UPTIME, - satMan = satelliteManager, - satelliteSupported = false, - ) + setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false) runCurrent() // THEN the repo registers for state changes of satellite support @@ -577,11 +560,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { fun satelliteNotSupported_supportShowsUp_registersListeners() = testScope.runTest { // GIVEN satellite is not supported - setUpRepo( - uptime = MIN_UPTIME, - satMan = satelliteManager, - satelliteSupported = false, - ) + setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false) runCurrent() val callback = @@ -610,11 +589,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { fun repoDoesNotCheckForSupportUntilMinUptime() = testScope.runTest { // GIVEN we init 100ms after sysui starts up - setUpRepo( - uptime = 100, - satMan = satelliteManager, - satelliteSupported = true, - ) + setUpRepo(uptime = 100, satMan = satelliteManager, satelliteSupported = true) // WHEN data is requested val connectionState by collectLastValue(underTest.connectionState) @@ -726,6 +701,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { logBuffer = FakeLogBuffer.Factory.create(), verboseLogBuffer = FakeLogBuffer.Factory.create(), systemClock, + context.resources, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt new file mode 100644 index 000000000000..778614b79fc8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt @@ -0,0 +1,75 @@ +/* + * 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 + +import org.junit.rules.TestWatcher +import org.junit.runner.Description +import org.junit.runners.model.MultipleFailureException + +/** + * Rule that allows teardown steps to be added right next to the places where it becomes clear they + * are needed. This can avoid the need for complicated or conditional logic in a single teardown + * method. Examples: + * ``` + * @get:Rule teardownRule = OnTeardownRule() + * + * // setup and teardown right next to each other + * @Before + * fun setUp() { + * val oldTimeout = getGlobalTimeout() + * teardownRule.onTeardown { setGlobalTimeout(oldTimeout) } + * overrideGlobalTimeout(5000) + * } + * + * // add teardown logic for fixtures that aren't used in every test + * fun addCustomer() { + * val id = globalDatabase.addCustomer(TEST_NAME, TEST_ADDRESS, ...) + * teardownRule.onTeardown { globalDatabase.deleteCustomer(id) } + * } + * ``` + */ +class OnTeardownRule : TestWatcher() { + private var canAdd = true + private val teardowns = mutableListOf<() -> Unit>() + + fun onTeardown(teardownRunnable: () -> Unit) { + if (!canAdd) { + throw IllegalStateException("Cannot add new teardown routines after test complete.") + } + teardowns.add(teardownRunnable) + } + + fun onTeardown(teardownRunnable: Runnable) { + if (!canAdd) { + throw IllegalStateException("Cannot add new teardown routines after test complete.") + } + teardowns.add { teardownRunnable.run() } + } + + override fun finished(description: Description?) { + canAdd = false + val errors = mutableListOf<Throwable>() + teardowns.reversed().forEach { + try { + it() + } catch (e: Throwable) { + errors.add(e) + } + } + MultipleFailureException.assertEmpty(errors) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 27a2cab1448e..153a8be06adc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -56,6 +56,8 @@ import org.mockito.Mockito; import java.io.FileInputStream; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Future; @@ -69,6 +71,17 @@ import java.util.concurrent.Future; // background on Ravenwood is available at go/ravenwood-docs @DisabledOnRavenwood public abstract class SysuiTestCase { + /** + * Especially when self-testing test utilities, we may have classes that look like test + * classes, but we don't expect to ever actually run as a top-level test. + * For example, {@link com.android.systemui.TryToDoABadThing}. + * Verifying properties on these as a part of structural tests like + * AAAPlusPlusVerifySysuiRequiredTestPropertiesTest is a waste of our time, and makes things + * look more confusing, so this lets us skip when appropriate. + */ + @Retention(RetentionPolicy.RUNTIME) + public @interface SkipSysuiVerification { + } private static final String TAG = "SysuiTestCase"; @@ -172,6 +185,15 @@ public abstract class SysuiTestCase { public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = new DexmakerShareClassLoaderRule(); + @Rule public final OnTeardownRule mTearDownRule = new OnTeardownRule(); + + /** + * Schedule a cleanup routine to happen when the test state is torn down. + */ + protected void onTeardown(Runnable tearDownRunnable) { + mTearDownRule.onTeardown(tearDownRunnable); + } + // set the highest order so it's the innermost rule @Rule(order = Integer.MAX_VALUE) public TestWithLooperRule mlooperRule = new TestWithLooperRule(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt index b27dadc9035b..3b175853de7d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt @@ -42,15 +42,6 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) : val id = nextWidgetId++ val providerInfo = createAppWidgetProviderInfo(provider, user.identifier) - fakeDatabase[id] = - CommunalWidgetContentModel.Available( - appWidgetId = id, - rank = rank ?: 0, - providerInfo = providerInfo, - spanY = 3, - ) - updateListFromDatabase() - val configured = configurator?.configureWidget(id) != false if (configured) { onConfigured(id, providerInfo, rank ?: -1) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt index ddae58168201..72cb1dfe38db 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt @@ -1,8 +1,10 @@ package com.android.systemui.kosmos import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos.Fixture import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -45,3 +47,5 @@ fun Kosmos.runTest(testBody: suspend Kosmos.() -> Unit) = testScope.runTest { this@runTest.testBody() } fun Kosmos.runCurrent() = testScope.runCurrent() + +fun <T> Kosmos.collectLastValue(flow: Flow<T>) = testScope.collectLastValue(flow) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index 5eaa198fb2a6..63e6eb6c0ef5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -51,6 +51,8 @@ import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor +import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel import com.android.systemui.model.sceneContainerPlugin import com.android.systemui.plugins.statusbar.statusBarStateController import com.android.systemui.power.data.repository.fakePowerRepository @@ -68,6 +70,8 @@ import com.android.systemui.shade.domain.interactor.shadeModeInteractor import com.android.systemui.shade.shadeController import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel +import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor @@ -106,6 +110,7 @@ class KosmosJavaAdapter() { val bouncerRepository by lazy { kosmos.bouncerRepository } val communalRepository by lazy { kosmos.fakeCommunalSceneRepository } val communalTransitionViewModel by lazy { kosmos.communalTransitionViewModel } + val activeNotificationsInteractor by lazy { kosmos.activeNotificationsInteractor } val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor } val seenNotificationsInteractor by lazy { kosmos.seenNotificationsInteractor } val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } @@ -119,6 +124,7 @@ class KosmosJavaAdapter() { val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository } val simBouncerInteractor by lazy { kosmos.simBouncerInteractor } val statusBarStateController by lazy { kosmos.statusBarStateController } + val statusBarModePerDisplayRepository by lazy { kosmos.fakeStatusBarModePerDisplayRepository } val interactionJankMonitor by lazy { kosmos.interactionJankMonitor } val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig } val sceneInteractor by lazy { kosmos.sceneInteractor } @@ -164,4 +170,11 @@ class KosmosJavaAdapter() { val msdlPlayer by lazy { kosmos.fakeMSDLPlayer } val shadeModeInteractor by lazy { kosmos.shadeModeInteractor } val bouncerHapticHelper by lazy { kosmos.bouncerHapticPlayer } + + val glanceableHubToLockscreenTransitionViewModel by lazy { + kosmos.glanceableHubToLockscreenTransitionViewModel + } + val lockscreenToGlanceableHubTransitionViewModel by lazy { + kosmos.lockscreenToGlanceableHubTransitionViewModel + } } 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 new file mode 100644 index 000000000000..7e7eea216584 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.controls.domain.pipeline + +import com.android.systemui.kosmos.Kosmos +import org.mockito.kotlin.mock + +var Kosmos.media3ActionFactory: Media3ActionFactory by Kosmos.Fixture { mock {} } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt index cb7750f55647..af6a0c505535 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt @@ -34,6 +34,7 @@ val Kosmos.mediaDataLoader by fakeMediaControllerFactory, mediaFlags, imageLoader, - statusBarManager + statusBarManager, + media3ActionFactory, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt index 7f8348e2ca6f..b833750a2c4a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt @@ -18,21 +18,32 @@ package com.android.systemui.media.controls.util import android.content.Context import android.media.session.MediaController -import android.media.session.MediaSession import android.media.session.MediaSession.Token +import android.os.Looper +import androidx.media3.session.MediaController as Media3Controller +import androidx.media3.session.SessionToken class FakeMediaControllerFactory(context: Context) : MediaControllerFactory(context) { private val mediaControllersForToken = mutableMapOf<Token, MediaController>() + private var media3Controller: Media3Controller? = null - override fun create(token: MediaSession.Token): android.media.session.MediaController { + override fun create(token: Token): MediaController { if (token !in mediaControllersForToken) { super.create(token) } return mediaControllersForToken[token]!! } + override suspend fun create(token: SessionToken, looper: Looper): Media3Controller { + return media3Controller ?: super.create(token, looper) + } + fun setControllerForToken(token: Token, mediaController: MediaController) { mediaControllersForToken[token] = mediaController } + + fun setMedia3Controller(mediaController: Media3Controller) { + media3Controller = mediaController + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt new file mode 100644 index 000000000000..94e0bca5675b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.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.media.controls.util + +import android.content.Context +import android.media.session.MediaSession.Token +import androidx.media3.session.SessionToken + +class FakeSessionTokenFactory(context: Context) : SessionTokenFactory(context) { + private var sessionToken: SessionToken? = null + + override suspend fun createTokenFromLegacy(token: Token): SessionToken { + return sessionToken ?: super.createTokenFromLegacy(token) + } + + fun setMedia3SessionToken(token: SessionToken) { + sessionToken = token + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt new file mode 100644 index 000000000000..8e473042c5d4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.controls.util + +import android.content.applicationContext +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeSessionTokenFactory by Kosmos.Fixture { FakeSessionTokenFactory(applicationContext) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt index f66125a6087e..6787b8ebb37f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt @@ -52,7 +52,7 @@ val Kosmos.customTileViewModelFactory: QSTileViewModelFactory.Component by ) object : QSTileViewModel { override val state: StateFlow<QSTileState?> = - MutableStateFlow(QSTileState.build({ null }, tileSpec.spec) {}) + MutableStateFlow(QSTileState.build(null, tileSpec.spec) {}) override val config: QSTileConfig = config override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt index 5b6fd8c3bd62..ab1c1818bf80 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt @@ -44,7 +44,7 @@ private constructor(failureMetadata: FailureMetadata, subject: QSTileState?) : check("other").that(other).isNotNull() other ?: return } - check("icon").that(actual.icon()).isEqualTo(other.icon()) + check("icon").that(actual.icon).isEqualTo(other.icon) check("iconRes").that(actual.iconRes).isEqualTo(other.iconRes) check("label").that(actual.label).isEqualTo(other.label) check("activationState").that(actual.activationState).isEqualTo(other.activationState) 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/statusbar/core/FakeStatusBarInitializerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt index 8c218be6c982..50a19a9bc68a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt @@ -16,11 +16,13 @@ package com.android.systemui.statusbar.core +import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository import com.android.systemui.statusbar.window.StatusBarWindowController class FakeStatusBarInitializerFactory() : StatusBarInitializer.Factory { override fun create( - statusBarWindowController: StatusBarWindowController + statusBarWindowController: StatusBarWindowController, + statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository, ): StatusBarInitializer = FakeStatusBarInitializer() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt index 303529b7f7b0..6e990277df6b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.core import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore val Kosmos.fakeStatusBarInitializer by Kosmos.Fixture { FakeStatusBarInitializer() } @@ -37,6 +38,7 @@ val Kosmos.multiDisplayStatusBarInitializerStore by displayRepository, fakeStatusBarInitializerFactory, fakeStatusBarWindowControllerStore, + fakeStatusBarModeRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt index 285cebb96cae..8712b02ec884 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt @@ -20,8 +20,10 @@ import android.view.Display import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.data.model.StatusBarAppearance import com.android.systemui.statusbar.data.model.StatusBarMode +import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent import dagger.Binds import dagger.Module +import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow @@ -53,6 +55,14 @@ class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository override fun clearTransient() { isTransientShown.value = false } + + override fun start() {} + + override fun stop() {} + + override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {} + + override fun dump(pw: PrintWriter, args: Array<out String>) {} } @Module 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 new file mode 100644 index 000000000000..5f337326b546 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.data.repository + +import com.android.systemui.display.data.repository.displayRepository +import com.android.systemui.display.data.repository.displayScopeRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import org.mockito.kotlin.mock + +val Kosmos.lightBarControllerStoreImpl by + Kosmos.Fixture { + LightBarControllerStoreImpl( + backgroundApplicationScope = applicationCoroutineScope, + displayRepository = displayRepository, + factory = { _, _, _ -> mock() }, + displayScopeRepository = displayScopeRepository, + statusBarModeRepositoryStore = statusBarModeRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt index 12db2f74197d..a5856020d6a4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt @@ -16,7 +16,10 @@ package com.android.systemui.statusbar.data.repository +import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import org.mockito.kotlin.mock val Kosmos.fakeStatusBarModePerDisplayRepository by Kosmos.Fixture { FakeStatusBarModePerDisplayRepository() } @@ -24,3 +27,21 @@ val Kosmos.fakeStatusBarModePerDisplayRepository by val Kosmos.statusBarModeRepository: StatusBarModeRepositoryStore by Kosmos.Fixture { fakeStatusBarModeRepository } val Kosmos.fakeStatusBarModeRepository by Kosmos.Fixture { FakeStatusBarModeRepository() } +val Kosmos.fakeStatusBarModePerDisplayRepositoryFactory by + Kosmos.Fixture { FakeStatusBarModePerDisplayRepositoryFactory() } + +val Kosmos.multiDisplayStatusBarModeRepositoryStore by + Kosmos.Fixture { + MultiDisplayStatusBarModeRepositoryStore( + applicationCoroutineScope, + fakeStatusBarModePerDisplayRepositoryFactory, + displayRepository, + ) + } + +class FakeStatusBarModePerDisplayRepositoryFactory : StatusBarModePerDisplayRepositoryFactory { + + override fun create(displayId: Int): StatusBarModePerDisplayRepositoryImpl { + return mock<StatusBarModePerDisplayRepositoryImpl>() + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt index 76bdc0de3d7b..32c582f79ed7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt @@ -28,6 +28,7 @@ fun activeNotificationModel( key: String, groupKey: String? = null, whenTime: Long = 0L, + isPromoted: Boolean = false, isAmbient: Boolean = false, isRowDismissed: Boolean = false, isSilent: Boolean = false, @@ -50,6 +51,7 @@ fun activeNotificationModel( key = key, groupKey = groupKey, whenTime = whenTime, + isPromoted = isPromoted, isAmbient = isAmbient, isRowDismissed = isRowDismissed, isSilent = isSilent, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt index f7acae9846df..067193fb7aa9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt @@ -19,8 +19,13 @@ package com.android.systemui.statusbar.notification.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.notification.collection.provider.sectionStyleProvider import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider val Kosmos.renderNotificationListInteractor by Kosmos.Fixture { - RenderNotificationListInteractor(activeNotificationListRepository, sectionStyleProvider) + RenderNotificationListInteractor( + activeNotificationListRepository, + sectionStyleProvider, + promotedNotificationsProvider, + ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt index 94b2bdf63608..a7aa0b41a7aa 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.android.systemui.util.settings +package com.android.systemui.statusbar.notification.promoted import com.android.systemui.kosmos.Kosmos -val Kosmos.userAwareSecureSettingsRepository by - Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() } +val Kosmos.promotedNotificationsProvider by Kosmos.Fixture { PromotedNotificationsProviderImpl() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt new file mode 100644 index 000000000000..01175a568b3a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.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.statusbar.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.user.domain.interactor.userSwitcherInteractor +import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel + +val Kosmos.statusBarUserChipViewModel: StatusBarUserChipViewModel by + Kosmos.Fixture { StatusBarUserChipViewModel(userSwitcherInteractor) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt new file mode 100644 index 000000000000..bf66cb6e8ecc --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.concurrency + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeExecution: FakeExecution by + Kosmos.Fixture { FakeExecution().apply { simulateMainThread = false } } +var Kosmos.execution: Execution by Kosmos.Fixture { fakeExecution } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt index 5054e29534e9..dc10ca9fd0a5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt @@ -14,22 +14,21 @@ * limitations under the License. */ -package com.android.systemui.util.settings +package com.android.systemui.util.settings.data.repository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.user.data.repository.userRepository +import com.android.systemui.util.settings.fakeSettings import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.map -class FakeUserAwareSecureSettingsRepository : UserAwareSecureSettingsRepository { - - private val settings = MutableStateFlow<Map<String, Boolean>>(mutableMapOf()) - - override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> { - return settings.map { it.getOrDefault(name, defaultValue) } - } - - fun setBoolSettingForActiveUser(name: String, value: Boolean) { - settings.value = settings.value.toMutableMap().apply { this[name] = value } +val Kosmos.userAwareSecureSettingsRepository by + Kosmos.Fixture { + UserAwareSecureSettingsRepository( + fakeSettings, + userRepository, + testDispatcher, + backgroundCoroutineContext, + ) } -}
\ No newline at end of file diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt new file mode 100644 index 000000000000..ff77908a9b94 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.settings.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.user.data.repository.userRepository +import com.android.systemui.util.settings.fakeSettings +import com.android.systemui.util.settings.repository.UserAwareSystemSettingsRepository + +val Kosmos.userAwareSystemSettingsRepository by + Kosmos.Fixture { + UserAwareSystemSettingsRepository( + fakeSettings, + userRepository, + testDispatcher, + backgroundCoroutineContext, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt index 1b58582a806f..ed5322ed098e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt @@ -21,6 +21,7 @@ import android.content.pm.ApplicationInfo import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.media.mediaOutputDialogManager +import com.android.systemui.util.concurrency.execution import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -53,6 +54,7 @@ val Kosmos.mediaOutputInteractor by testScope.testScheduler, mediaControllerRepository, mediaControllerInteractor, + execution, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt index 652b3ea984e7..fdeb8cef02b9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import android.os.Handler import android.os.looper import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope var Kosmos.mediaControllerInteractor: MediaControllerInteractor by - Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper)) } + Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper), testScope.testScheduler) } 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/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java index 66a6890a23b0..869d854f7b23 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java @@ -26,12 +26,10 @@ import android.annotation.Nullable; import android.os.Bundle; import android.platform.test.annotations.RavenwoodTestRunnerInitializing; import android.platform.test.annotations.internal.InnerRunner; -import android.platform.test.ravenwood.RavenwoodTestStats.Result; import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; -import org.junit.AssumptionViolatedException; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runner.Runner; @@ -171,10 +169,11 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase final var notifier = new RavenwoodRunNotifier(realNotifier); final var description = getDescription(); + RavenwoodTestStats.getInstance().attachToRunNotifier(notifier); + if (mRealRunner instanceof ClassSkippingTestRunner) { - mRealRunner.run(notifier); Log.i(TAG, "onClassSkipped: description=" + description); - RavenwoodTestStats.getInstance().onClassSkipped(description); + mRealRunner.run(notifier); return; } @@ -205,7 +204,6 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase if (!skipRunnerHook) { try { - RavenwoodTestStats.getInstance().onClassFinished(description); mState.exitTestClass(); } catch (Throwable th) { notifier.reportAfterTestFailure(th); @@ -295,8 +293,6 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase // method-level annotations here. if (scope == Scope.Instance && order == Order.Outer) { if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(description, true)) { - RavenwoodTestStats.getInstance().onTestFinished( - classDescription, description, Result.Skipped); return false; } } @@ -317,16 +313,6 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase // End of a test method. mState.exitTestMethod(); - final Result result; - if (th == null) { - result = Result.Passed; - } else if (th instanceof AssumptionViolatedException) { - result = Result.Skipped; - } else { - result = Result.Failed; - } - - RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result); } // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error. 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 0f163524d2fe..28c262d53ff1 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -40,6 +40,7 @@ import android.os.Build; import android.os.Bundle; import android.os.HandlerThread; import android.os.Looper; +import android.os.Process_ravenwood; import android.os.ServiceManager; import android.os.SystemProperties; import android.provider.DeviceConfig_host; @@ -52,6 +53,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.hoststubgen.hosthelper.HostTestUtils; import com.android.internal.os.RuntimeInit; import com.android.ravenwood.RavenwoodRuntimeNative; +import com.android.ravenwood.RavenwoodRuntimeState; import com.android.ravenwood.common.RavenwoodCommonUtils; import com.android.ravenwood.common.RavenwoodRuntimeException; import com.android.ravenwood.common.SneakyThrow; @@ -199,7 +201,7 @@ public class RavenwoodRuntimeEnvironmentController { */ public static void init(RavenwoodAwareTestRunner runner) { if (RAVENWOOD_VERBOSE_LOGGING) { - Log.i(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE")); + Log.v(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE")); } if (sRunner == runner) { return; @@ -223,7 +225,9 @@ public class RavenwoodRuntimeEnvironmentController { Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler); } - android.os.Process.init$ravenwood(config.mUid, config.mPid); + RavenwoodRuntimeState.sUid = config.mUid; + RavenwoodRuntimeState.sPid = config.mPid; + RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel; sOriginalIdentityToken = Binder.clearCallingIdentity(); reinit(); setSystemProperties(config.mSystemProperties); @@ -310,7 +314,7 @@ public class RavenwoodRuntimeEnvironmentController { */ public static void reset() { if (RAVENWOOD_VERBOSE_LOGGING) { - Log.i(TAG, "reset() called here", new RuntimeException("STACKTRACE")); + Log.v(TAG, "reset() called here", new RuntimeException("STACKTRACE")); } if (sRunner == null) { throw new RavenwoodRuntimeException("Internal error: reset() already called"); @@ -350,8 +354,8 @@ public class RavenwoodRuntimeEnvironmentController { if (sOriginalIdentityToken != -1) { Binder.restoreCallingIdentity(sOriginalIdentityToken); } - android.os.Process.reset$ravenwood(); - + RavenwoodRuntimeState.reset(); + Process_ravenwood.reset(); DeviceConfig_host.reset(); try { diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java index 016de8e45291..787058545fed 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java @@ -18,6 +18,9 @@ package android.platform.test.ravenwood; import android.util.Log; import org.junit.runner.Description; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; +import org.junit.runner.notification.RunNotifier; import java.io.File; import java.io.IOException; @@ -27,7 +30,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; /** @@ -39,7 +42,7 @@ import java.util.Map; */ public class RavenwoodTestStats { private static final String TAG = "RavenwoodTestStats"; - private static final String HEADER = "Module,Class,ClassDesc,Passed,Failed,Skipped"; + private static final String HEADER = "Module,Class,OuterClass,Passed,Failed,Skipped"; private static RavenwoodTestStats sInstance; @@ -66,7 +69,7 @@ public class RavenwoodTestStats { private final PrintWriter mOutputWriter; private final String mTestModuleName; - public final Map<Description, Map<Description, Result>> mStats = new HashMap<>(); + public final Map<String, Map<String, Result>> mStats = new LinkedHashMap<>(); /** Ctor */ public RavenwoodTestStats() { @@ -115,75 +118,129 @@ public class RavenwoodTestStats { return cwd.getName(); } - private void addResult(Description classDescription, Description methodDescription, + private void addResult(String className, String methodName, Result result) { - mStats.compute(classDescription, (classDesc, value) -> { + mStats.compute(className, (className_, value) -> { if (value == null) { - value = new HashMap<>(); + value = new LinkedHashMap<>(); + } + // If the result is already set, don't overwrite it. + if (!value.containsKey(methodName)) { + value.put(methodName, result); } - value.put(methodDescription, result); return value; }); } /** - * Call it when a test class is skipped. - */ - public void onClassSkipped(Description classDescription) { - addResult(classDescription, Description.EMPTY, Result.Skipped); - onClassFinished(classDescription); - } - - /** * Call it when a test method is finished. */ - public void onTestFinished(Description classDescription, Description testDescription, - Result result) { - addResult(classDescription, testDescription, result); + private void onTestFinished(String className, String testName, Result result) { + addResult(className, testName, result); } /** - * Call it when a test class is finished. + * Dump all the results and clear it. */ - public void onClassFinished(Description classDescription) { - int passed = 0; - int skipped = 0; - int failed = 0; - var stats = mStats.get(classDescription); - if (stats == null) { - return; - } - for (var e : stats.values()) { - switch (e) { - case Passed: passed++; break; - case Skipped: skipped++; break; - case Failed: failed++; break; + private void dumpAllAndClear() { + for (var entry : mStats.entrySet()) { + int passed = 0; + int skipped = 0; + int failed = 0; + var className = entry.getKey(); + + for (var e : entry.getValue().values()) { + switch (e) { + case Passed: + passed++; + break; + case Skipped: + skipped++; + break; + case Failed: + failed++; + break; + } } + + mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n", + mTestModuleName, className, getOuterClassName(className), + passed, failed, skipped); } + mOutputWriter.flush(); + mStats.clear(); + } - var testClass = extractTestClass(classDescription); + private static String getOuterClassName(String className) { + // Just delete the '$', because I'm not sure if the className we get here is actaully a + // valid class name that does exist. (it might have a parameter name, etc?) + int p = className.indexOf('$'); + if (p < 0) { + return className; + } + return className.substring(0, p); + } - mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n", - mTestModuleName, (testClass == null ? "?" : testClass.getCanonicalName()), - classDescription, passed, failed, skipped); - mOutputWriter.flush(); + public void attachToRunNotifier(RunNotifier notifier) { + notifier.addListener(mRunListener); } - /** - * Try to extract the class from a description, which is needed because - * ParameterizedAndroidJunit4's description doesn't contain a class. - */ - private Class<?> extractTestClass(Description desc) { - if (desc.getTestClass() != null) { - return desc.getTestClass(); + private final RunListener mRunListener = new RunListener() { + @Override + public void testSuiteStarted(Description description) { + Log.d(TAG, "testSuiteStarted: " + description); } - // Look into the children. - for (var child : desc.getChildren()) { - var fromChild = extractTestClass(child); - if (fromChild != null) { - return fromChild; - } + + @Override + public void testSuiteFinished(Description description) { + Log.d(TAG, "testSuiteFinished: " + description); } - return null; - } + + @Override + public void testRunStarted(Description description) { + Log.d(TAG, "testRunStarted: " + description); + } + + @Override + public void testRunFinished(org.junit.runner.Result result) { + Log.d(TAG, "testRunFinished: " + result); + + dumpAllAndClear(); + } + + @Override + public void testStarted(Description description) { + Log.d(TAG, " testStarted: " + description); + } + + @Override + public void testFinished(Description description) { + Log.d(TAG, " testFinished: " + description); + + // Send "Passed", but if there's already another result sent for this, this won't + // override it. + onTestFinished(description.getClassName(), description.getMethodName(), Result.Passed); + } + + @Override + public void testFailure(Failure failure) { + Log.d(TAG, " testFailure: " + failure); + + var description = failure.getDescription(); + onTestFinished(description.getClassName(), description.getMethodName(), Result.Failed); + } + + @Override + public void testAssumptionFailure(Failure failure) { + Log.d(TAG, " testAssumptionFailure: " + failure); + var description = failure.getDescription(); + onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped); + } + + @Override + public void testIgnored(Description description) { + Log.d(TAG, " testIgnored: " + description); + onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped); + } + }; } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java index 37b0abcceede..d8f2b705d539 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Instrumentation; import android.content.Context; +import android.os.Build; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -67,7 +68,7 @@ public final class RavenwoodConfig { String mTargetPackageName; int mMinSdkLevel; - int mTargetSdkLevel; + int mTargetSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT; boolean mProvideMainThread = false; diff --git a/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java new file mode 100644 index 000000000000..3c6a4d78d1d9 --- /dev/null +++ b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java @@ -0,0 +1,70 @@ +/* + * 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.os; + +import android.util.Pair; + +public class Process_ravenwood { + + private static volatile ThreadLocal<Pair<Integer, Boolean>> sThreadPriority; + + static { + reset(); + } + + public static void reset() { + // Reset the thread local variable + sThreadPriority = ThreadLocal.withInitial( + () -> Pair.create(Process.THREAD_PRIORITY_DEFAULT, true)); + } + + /** + * Called by {@link Process#setThreadPriority(int, int)} + */ + public static void setThreadPriority(int tid, int priority) { + if (Process.myTid() == tid) { + boolean backgroundOk = sThreadPriority.get().second; + if (priority >= Process.THREAD_PRIORITY_BACKGROUND && !backgroundOk) { + throw new IllegalArgumentException( + "Priority " + priority + " blocked by setCanSelfBackground()"); + } + sThreadPriority.set(Pair.create(priority, backgroundOk)); + } else { + throw new UnsupportedOperationException( + "Cross-thread priority management not yet available in Ravenwood"); + } + } + + /** + * Called by {@link Process#setCanSelfBackground(boolean)} + */ + public static void setCanSelfBackground(boolean backgroundOk) { + int priority = sThreadPriority.get().first; + sThreadPriority.set(Pair.create(priority, backgroundOk)); + } + + /** + * Called by {@link Process#getThreadPriority(int)} + */ + public static int getThreadPriority(int tid) { + if (Process.myTid() == tid) { + return sThreadPriority.get().first; + } else { + throw new UnsupportedOperationException( + "Cross-thread priority management not yet available in Ravenwood"); + } + } +} diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java deleted file mode 100644 index c18c307ad1e3..000000000000 --- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.os; - -import java.util.Arrays; -import java.util.HashMap; - -public class LongArrayContainer_host { - private static final HashMap<Long, long[]> sInstances = new HashMap<>(); - private static long sNextId = 1; - - public static long native_init(int arrayLength) { - long[] array = new long[arrayLength]; - long instanceId = sNextId++; - sInstances.put(instanceId, array); - return instanceId; - } - - static long[] getInstance(long instanceId) { - return sInstances.get(instanceId); - } - - public static void native_setValues(long instanceId, long[] values) { - System.arraycopy(values, 0, getInstance(instanceId), 0, values.length); - } - - public static void native_getValues(long instanceId, long[] values) { - System.arraycopy(getInstance(instanceId), 0, values, 0, values.length); - } - - public static boolean native_combineValues(long instanceId, long[] array, int[] indexMap) { - long[] values = getInstance(instanceId); - - boolean nonZero = false; - Arrays.fill(array, 0); - - for (int i = 0; i < values.length; i++) { - int index = indexMap[i]; - if (index < 0 || index >= array.length) { - throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: [0, " - + (array.length - 1) + "]"); - } - if (values[i] != 0) { - array[index] += values[i]; - nonZero = true; - } - } - return nonZero; - } -} diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java index 9ce8ea8e16ef..90608f6e87d1 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java @@ -286,15 +286,12 @@ public class LongArrayMultiStateCounter_host { return getInstance(instanceId).mArrayLength; } - public static void native_setValues(long instanceId, int state, long containerInstanceId) { - getInstance(instanceId).setValue(state, - LongArrayContainer_host.getInstance(containerInstanceId)); + public static void native_setValues(long instanceId, int state, long[] values) { + getInstance(instanceId).setValue(state, values); } - public static void native_updateValues(long instanceId, long containerInstanceId, - long timestampMs) { - getInstance(instanceId).updateValue( - LongArrayContainer_host.getInstance(containerInstanceId), timestampMs); + public static void native_updateValues(long instanceId, long[] values, long timestampMs) { + getInstance(instanceId).updateValue(values, timestampMs); } public static void native_setState(long instanceId, int state, long timestampMs) { @@ -305,19 +302,16 @@ public class LongArrayMultiStateCounter_host { getInstance(targetInstanceId).copyStatesFrom(getInstance(sourceInstanceId)); } - public static void native_incrementValues(long instanceId, long containerInstanceId, - long timestampMs) { - getInstance(instanceId).incrementValues( - LongArrayContainer_host.getInstance(containerInstanceId), timestampMs); + public static void native_incrementValues(long instanceId, long[] delta, long timestampMs) { + getInstance(instanceId).incrementValues(delta, timestampMs); } - public static void native_addCounts(long instanceId, long containerInstanceId) { - getInstance(instanceId).addCounts(LongArrayContainer_host.getInstance(containerInstanceId)); + public static void native_addCounts(long instanceId, long[] counts) { + getInstance(instanceId).addCounts(counts); } - public static void native_getCounts(long instanceId, long containerInstanceId, int state) { - getInstance(instanceId).getValues(LongArrayContainer_host.getInstance(containerInstanceId), - state); + public static void native_getCounts(long instanceId, long[] counts, int state) { + getInstance(instanceId).getValues(counts, state); } public static void native_reset(long instanceId) { diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java index e12ff240c4d9..b65668b67e03 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java @@ -23,14 +23,6 @@ public class RavenwoodEnvironment_host { } /** - * Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}. - */ - public static void ensureRavenwoodInitialized() { - // Initialization is now done by RavenwoodAwareTestRunner. - // Should we remove it? - } - - /** * Called from {@link RavenwoodEnvironment#getRavenwoodRuntimePath()}. */ public static String getRavenwoodRuntimePath(RavenwoodEnvironment env) { diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java index c94ef31a5e5e..02981713674d 100644 --- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java +++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java @@ -16,6 +16,7 @@ package android.system; import com.android.ravenwood.RavenwoodRuntimeNative; +import com.android.ravenwood.RavenwoodRuntimeState; import com.android.ravenwood.common.JvmWorkaround; import java.io.FileDescriptor; @@ -97,4 +98,16 @@ public final class Os { public static void setenv(String name, String value, boolean overwrite) throws ErrnoException { RavenwoodRuntimeNative.setenv(name, value, overwrite); } + + public static int getpid() { + return RavenwoodRuntimeState.sPid; + } + + public static int getuid() { + return RavenwoodRuntimeState.sUid; + } + + public static int gettid() { + return RavenwoodRuntimeNative.gettid(); + } } diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java index f13189f6f8be..7b940b423b69 100644 --- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java +++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java @@ -58,6 +58,8 @@ public class RavenwoodRuntimeNative { public static native void clearSystemProperties(); + public static native int gettid(); + public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence); } diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java new file mode 100644 index 000000000000..175e020d61d6 --- /dev/null +++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java @@ -0,0 +1,35 @@ +/* + * 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.ravenwood; + +public class RavenwoodRuntimeState { + // This must match VMRuntime.SDK_VERSION_CUR_DEVELOPMENT. + public static final int CUR_DEVELOPMENT = 10000; + + public static volatile int sUid; + public static volatile int sPid; + public static volatile int sTargetSdkLevel; + + static { + reset(); + } + + public static void reset() { + sUid = -1; + sPid = -1; + sTargetSdkLevel = CUR_DEVELOPMENT; + } +} diff --git a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java index ba89f71dde8a..eaadac6a8b92 100644 --- a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java +++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java @@ -19,6 +19,7 @@ package dalvik.system; // The original is here: // $ANDROID_BUILD_TOP/libcore/libart/src/main/java/dalvik/system/VMRuntime.java +import com.android.ravenwood.RavenwoodRuntimeState; import com.android.ravenwood.common.JvmWorkaround; import java.lang.reflect.Array; @@ -52,4 +53,8 @@ public class VMRuntime { public long addressOf(Object obj) { return JvmWorkaround.getInstance().addressOf(obj); } + + public int getTargetSdkVersion() { + return RavenwoodRuntimeState.sTargetSdkLevel; + } } diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp index 2a3c26ed3ea3..5b75e9854758 100644 --- a/ravenwood/runtime-jni/ravenwood_runtime.cpp +++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp @@ -17,6 +17,7 @@ #include <fcntl.h> #include <string.h> #include <sys/stat.h> +#include <sys/syscall.h> #include <unistd.h> #include <utils/misc.h> @@ -173,6 +174,12 @@ static void Linux_setenv(JNIEnv* env, jobject, jstring javaName, jstring javaVal throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0)); } + +static jint Linux_gettid(JNIEnv* env, jobject) { + // gettid(2() was added in glibc 2.30 but Android uses an older version in prebuilt. + return syscall(__NR_gettid); +} + static void maybeRedirectLog() { auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT"); if (ravenwoodLogOut == NULL) { @@ -207,6 +214,7 @@ static const JNINativeMethod sMethods[] = { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat }, { "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open }, { "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv }, + { "gettid", "()I", (void*)Linux_gettid }, }; extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh index 5d623e0b6c36..1910100a7f5d 100755 --- a/ravenwood/scripts/run-ravenwood-tests.sh +++ b/ravenwood/scripts/run-ravenwood-tests.sh @@ -18,12 +18,38 @@ # Options: # # -s: "Smoke" test -- skip slow tests (SysUI, ICU) +# +# -x PCRE: Specify exclusion filter in PCRE +# Example: -x '^(Cts|hoststub)' # Exclude CTS and hoststubgen tests. +# +# -f PCRE: Specify inclusion filter in PCRE + + +# Regex to identify slow tests, in PCRE +SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood|CarSystemUIRavenTests)$' smoke=0 -while getopts "s" opt; do +include_re="" +exclude_re="" +smoke_exclude_re="" +dry_run="" +while getopts "sx:f:d" opt; do case "$opt" in s) - smoke=1 + # Remove slow tests. + smoke_exclude_re="$SLOW_TEST_RE" + ;; + x) + # Take a PCRE from the arg, and use it as an exclusion filter. + exclude_re="$OPTARG" + ;; + f) + # Take a PCRE from the arg, and use it as an inclusion filter. + include_re="$OPTARG" + ;; + d) + # Dry run + dry_run="echo" ;; '?') exit 1 @@ -35,21 +61,46 @@ shift $(($OPTIND - 1)) all_tests=(hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test ravenwood-stats-checker) all_tests+=( $(${0%/*}/list-ravenwood-tests.sh) ) -# Regex to identify slow tests, in PCRE -slow_tests_re='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood)$' - -if (( $smoke )) ; then - # Remove the slow tests. - all_tests=( $( - for t in "${all_tests[@]}"; do - echo $t | grep -vP "$slow_tests_re" - done - ) ) -fi +filter() { + local re="$1" + local grep_arg="$2" + if [[ "$re" == "" ]] ; then + cat # No filtering + else + grep $grep_arg -iP "$re" + fi +} -run() { - echo "Running: $*" - "${@}" +filter_in() { + filter "$1" } -run ${ATEST:-atest} "${all_tests[@]}" +filter_out() { + filter "$1" -v +} + + +# Remove the slow tests. +targets=( $( + for t in "${all_tests[@]}"; do + echo $t | filter_in "$include_re" | filter_out "$smoke_exclude_re" | filter_out "$exclude_re" + done +) ) + +# Show the target tests + +echo "Target tests:" +for t in "${targets[@]}"; do + echo " $t" +done + +# Calculate the removed tests. + +diff="$(diff <(echo "${all_tests[@]}" | tr ' ' '\n') <(echo "${targets[@]}" | tr ' ' '\n') )" + +if [[ "$diff" != "" ]]; then + echo "Excluded tests:" + echo "$diff" +fi + +$dry_run ${ATEST:-atest} "${targets[@]}" diff --git a/ravenwood/tests/runtime-test/Android.bp b/ravenwood/tests/runtime-test/Android.bp index 410292001670..0c0df1f993aa 100644 --- a/ravenwood/tests/runtime-test/Android.bp +++ b/ravenwood/tests/runtime-test/Android.bp @@ -10,6 +10,9 @@ package { android_ravenwood_test { name: "RavenwoodRuntimeTest", + libs: [ + "ravenwood-helper-runtime", + ], static_libs: [ "androidx.annotation_annotation", "androidx.test.ext.junit", diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java new file mode 100644 index 000000000000..8e04b698c9d9 --- /dev/null +++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java @@ -0,0 +1,66 @@ +/* + * 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.runtimetest; + +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; +import static android.os.Process.FIRST_APPLICATION_UID; + +import static org.junit.Assert.assertEquals; + +import android.os.Binder; +import android.os.Build; +import android.os.Process; +import android.platform.test.ravenwood.RavenwoodConfig; +import android.system.Os; + +import com.android.ravenwood.RavenwoodRuntimeState; + +import dalvik.system.VMRuntime; + +import org.junit.Test; + +public class IdentityTest { + + @RavenwoodConfig.Config + public static final RavenwoodConfig sConfig = + new RavenwoodConfig.Builder() + .setTargetSdkLevel(UPSIDE_DOWN_CAKE) + .setProcessApp() + .build(); + + @Test + public void testUid() { + assertEquals(FIRST_APPLICATION_UID, RavenwoodRuntimeState.sUid); + assertEquals(FIRST_APPLICATION_UID, Os.getuid()); + assertEquals(FIRST_APPLICATION_UID, Process.myUid()); + assertEquals(FIRST_APPLICATION_UID, Binder.getCallingUid()); + } + + @Test + public void testPid() { + int pid = RavenwoodRuntimeState.sPid; + assertEquals(pid, Os.getpid()); + assertEquals(pid, Process.myPid()); + assertEquals(pid, Binder.getCallingPid()); + } + + @Test + public void testTargetSdkLevel() { + assertEquals(Build.VERSION_CODES.CUR_DEVELOPMENT, RavenwoodRuntimeState.CUR_DEVELOPMENT); + assertEquals(UPSIDE_DOWN_CAKE, RavenwoodRuntimeState.sTargetSdkLevel); + assertEquals(UPSIDE_DOWN_CAKE, VMRuntime.getRuntime().getTargetSdkVersion()); + } +} diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java index c2230c739ccf..c55506a0a10a 100644 --- a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java +++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java @@ -24,6 +24,8 @@ import static android.system.OsConstants.S_ISREG; import static android.system.OsConstants.S_ISSOCK; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import static java.nio.file.LinkOption.NOFOLLOW_LINKS; @@ -51,10 +53,12 @@ import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFilePermission; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) public class OsTest { + public interface ConsumerWithThrow<T> { void accept(T var1) throws Exception; } @@ -165,6 +169,35 @@ public class OsTest { }); } + private static class TestThread extends Thread { + + final CountDownLatch mLatch = new CountDownLatch(1); + int mTid; + + TestThread() { + setDaemon(true); + } + + @Override + public void run() { + mTid = Os.gettid(); + mLatch.countDown(); + } + } + + @Test + public void testGetTid() throws InterruptedException { + var t1 = new TestThread(); + var t2 = new TestThread(); + t1.start(); + t2.start(); + // Wait for thread execution + assertTrue(t1.mLatch.await(1, TimeUnit.SECONDS)); + assertTrue(t2.mLatch.await(1, TimeUnit.SECONDS)); + // Make sure the tid is unique per-thread + assertNotEquals(t1.mTid, t2.mTid); + } + // Verify StructStat values from libcore against native JVM PosixFileAttributes private static void assertAttributesEqual(PosixFileAttributes attr, StructStat stat) { assertEquals(attr.lastModifiedTime(), convertTimespecToFileTime(stat.st_mtim)); diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java new file mode 100644 index 000000000000..d25b5c19f351 --- /dev/null +++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ravenwoodtest.runtimetest; + +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; +import static android.os.Process.THREAD_PRIORITY_DEFAULT; +import static android.os.Process.THREAD_PRIORITY_FOREGROUND; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import android.os.Process; +import android.system.Os; + +import org.junit.Test; + +public class ProcessTest { + + @Test + public void testGetUidPidTid() { + assertEquals(Os.getuid(), Process.myUid()); + assertEquals(Os.getpid(), Process.myPid()); + assertEquals(Os.gettid(), Process.myTid()); + } + + @Test + public void testThreadPriority() { + assertThrows(UnsupportedOperationException.class, + () -> Process.getThreadPriority(Process.myTid() + 1)); + assertThrows(UnsupportedOperationException.class, + () -> Process.setThreadPriority(Process.myTid() + 1, THREAD_PRIORITY_DEFAULT)); + assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid())); + Process.setThreadPriority(THREAD_PRIORITY_FOREGROUND); + assertEquals(THREAD_PRIORITY_FOREGROUND, Process.getThreadPriority(Process.myTid())); + Process.setCanSelfBackground(false); + Process.setThreadPriority(THREAD_PRIORITY_DEFAULT); + assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid())); + assertThrows(IllegalArgumentException.class, + () -> Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND)); + } +} diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 68ff9725ce5c..8567ccb9e7a5 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -17,6 +17,7 @@ package com.android.server.appwidget; import static android.appwidget.flags.Flags.remoteAdapterConversion; +import static android.appwidget.flags.Flags.remoteViewsProto; import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath; import static android.appwidget.flags.Flags.securityPolicyInteractAcrossUsers; import static android.appwidget.flags.Flags.supportResumeRestoreAfterReboot; @@ -31,6 +32,7 @@ import static com.android.server.appwidget.AppWidgetXmlUtil.serializeWidgetSizes import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.PermissionName; @@ -104,6 +106,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; import android.service.appwidget.AppWidgetServiceDumpProto; +import android.service.appwidget.GeneratedPreviewsProto; import android.service.appwidget.WidgetProto; import android.text.TextUtils; import android.util.ArrayMap; @@ -122,7 +125,9 @@ import android.util.SparseIntArray; import android.util.SparseLongArray; import android.util.TypedValue; import android.util.Xml; +import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; +import android.util.proto.ProtoUtils; import android.view.Display; import android.view.View; import android.widget.RemoteViews; @@ -134,6 +139,7 @@ import com.android.internal.app.UnlaunchableAppActivity; import com.android.internal.appwidget.IAppWidgetHost; import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; +import com.android.internal.infra.AndroidFuture; import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; @@ -221,6 +227,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // XML attribute for widget ids that are pending deletion. // See {@link Provider#pendingDeletedWidgetIds}. private static final String PENDING_DELETED_IDS_ATTR = "pending_deleted_ids"; + // Name of service directory in /data/system_ce/<user>/ + private static final String APPWIDGET_CE_DATA_DIRNAME = "appwidget"; + // Name of previews directory in /data/system_ce/<user>/appwidget/ + private static final String WIDGET_PREVIEWS_DIRNAME = "previews"; // Hard limit of number of hosts an app can create, note that the app that hosts the widgets // can have multiple instances of {@link AppWidgetHost}, typically in respect to different @@ -320,6 +330,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // Handler to the background thread that saves states to disk. private Handler mSaveStateHandler; + // Handler to the background thread that saves generated previews to disk. All operations that + // modify saved previews must be run on this Handler. + private Handler mSavePreviewsHandler; // Handler to the foreground thread that handles broadcasts related to user // and package events, as well as various internal events within // AppWidgetService. @@ -363,6 +376,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } else { mSaveStateHandler = BackgroundThread.getHandler(); } + mSavePreviewsHandler = new Handler(BackgroundThread.get().getLooper()); final ServiceThread serviceThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */); serviceThread.start(); @@ -382,7 +396,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_PROVIDERS, DEFAULT_GENERATED_PREVIEW_MAX_PROVIDERS); mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval, - generatedPreviewMaxCallsPerInterval, generatedPreviewsMaxProviders); + generatedPreviewMaxCallsPerInterval, + // Set a limit on the number of providers if storing them in memory. + remoteViewsProto() ? Integer.MAX_VALUE : generatedPreviewsMaxProviders); DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI, new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange); @@ -648,7 +664,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku for (int i = 0; i < providerCount; i++) { Provider provider = mProviders.get(i); if (provider.id.uid == clearedUid) { - changed |= provider.clearGeneratedPreviewsLocked(); + if (remoteViewsProto()) { + changed |= clearGeneratedPreviewsAsync(provider); + } else { + changed |= provider.clearGeneratedPreviewsLocked(); + } + if (DEBUG) { + Slog.e(TAG, "clearPreviewsForUidLocked " + provider + " changed " + changed); + } } } return changed; @@ -898,18 +921,30 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku for (int j = 0; j < widgetCount; j++) { Widget widget = provider.widgets.get(j); if (targetWidget != null && targetWidget != widget) continue; + // Identify the user in the host process since the intent will be invoked by + // the host app. + final Host host = widget.host; + final UserHandle hostUser; + if (host != null && host.id != null) { + hostUser = UserHandle.getUserHandleForUid(host.id.uid); + } else { + // Fallback to the parent profile if the host is null. + Slog.w(TAG, "Host is null when masking widget: " + widget.appWidgetId); + hostUser = mUserManager.getProfileParent(appUserId).getUserHandle(); + } if (provider.maskedByStoppedPackage) { Intent intent = createUpdateIntentLocked(provider, new int[] { widget.appWidgetId }); views.setOnClickPendingIntent(android.R.id.background, - PendingIntent.getBroadcast(mContext, widget.appWidgetId, + PendingIntent.getBroadcastAsUser(mContext, widget.appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT - | PendingIntent.FLAG_IMMUTABLE)); + | PendingIntent.FLAG_IMMUTABLE, hostUser)); } else if (onClickIntent != null) { views.setOnClickPendingIntent(android.R.id.background, - PendingIntent.getActivity(mContext, widget.appWidgetId, onClickIntent, - PendingIntent.FLAG_UPDATE_CURRENT - | PendingIntent.FLAG_IMMUTABLE)); + PendingIntent.getActivityAsUser(mContext, widget.appWidgetId, + onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT + | PendingIntent.FLAG_IMMUTABLE, null /* options */, + hostUser)); } if (widget.replaceWithMaskedViewsLocked(views)) { scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked()); @@ -3246,6 +3281,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku deleteWidgetsLocked(provider, UserHandle.USER_ALL); mProviders.remove(provider); mGeneratedPreviewsApiCounter.remove(provider.id); + if (remoteViewsProto()) { + clearGeneratedPreviewsAsync(provider); + } // no need to send the DISABLE broadcast, since the receiver is gone anyway cancelBroadcastsLocked(provider); @@ -3824,6 +3862,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } catch (IOException e) { Slog.w(TAG, "Failed to read state: " + e); } + + if (remoteViewsProto()) { + try { + loadGeneratedPreviewCategoriesLocked(profileId); + } catch (IOException e) { + Slog.w(TAG, "Failed to read preview categories: " + e); + } + } } if (version >= 0) { @@ -4593,6 +4639,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku keep.add(providerId); // Use the new AppWidgetProviderInfo. provider.setPartialInfoLocked(info); + // Clear old previews + if (remoteViewsProto()) { + clearGeneratedPreviewsAsync(provider); + } else { + provider.clearGeneratedPreviewsLocked(); + } // If it's enabled final int M = provider.widgets.size(); if (M > 0) { @@ -4884,6 +4936,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku mSecurityPolicy.enforceCallFromPackage(callingPackage); ensureWidgetCategoryCombinationIsValid(widgetCategory); + AndroidFuture<RemoteViews> result = null; synchronized (mLock) { ensureGroupStateLoadedLocked(profileId); final int providerCount = mProviders.size(); @@ -4917,10 +4970,23 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku callingPackage); if (providerIsInCallerProfile && !shouldFilterAppAccess && (providerIsInCallerPackage || hasBindAppWidgetPermission)) { - return provider.getGeneratedPreviewLocked(widgetCategory); + if (remoteViewsProto()) { + result = getGeneratedPreviewsAsync(provider, widgetCategory); + } else { + return provider.getGeneratedPreviewLocked(widgetCategory); + } } } } + + if (result != null) { + try { + return result.get(); + } catch (Exception e) { + Slog.e(TAG, "Failed to get generated previews Future result", e); + return null; + } + } // Either the provider does not exist or the caller does not have permission to access its // previews. return null; @@ -4950,8 +5016,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku providerComponent + " is not a valid AppWidget provider"); } if (mGeneratedPreviewsApiCounter.tryApiCall(providerId)) { - provider.setGeneratedPreviewLocked(widgetCategories, preview); - scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + if (remoteViewsProto()) { + setGeneratedPreviewsAsync(provider, widgetCategories, preview); + } else { + provider.setGeneratedPreviewLocked(widgetCategories, preview); + scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + } return true; } return false; @@ -4979,11 +5049,361 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku throw new IllegalArgumentException( providerComponent + " is not a valid AppWidget provider"); } - final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories); - if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + + if (remoteViewsProto()) { + removeGeneratedPreviewsAsync(provider, widgetCategories); + } else { + final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories); + if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + } } } + /** + * Return previews for the specified provider from a background thread. The result of the future + * is nullable. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @NonNull + private AndroidFuture<RemoteViews> getGeneratedPreviewsAsync( + @NonNull Provider provider, @AppWidgetProviderInfo.CategoryFlags int widgetCategory) { + AndroidFuture<RemoteViews> result = new AndroidFuture<>(); + mSavePreviewsHandler.post(() -> { + SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider); + for (int i = 0; i < previews.size(); i++) { + if ((widgetCategory & previews.keyAt(i)) != 0) { + result.complete(previews.valueAt(i)); + return; + } + } + result.complete(null); + }); + return result; + } + + /** + * Set previews for the specified provider on a background thread. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private void setGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories, + @NonNull RemoteViews preview) { + mSavePreviewsHandler.post(() -> { + SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider); + for (int flag : Provider.WIDGET_CATEGORY_FLAGS) { + if ((widgetCategories & flag) != 0) { + previews.put(flag, preview); + } + } + saveGeneratedPreviews(provider, previews, /* notify= */ true); + }); + } + + /** + * Remove previews for the specified provider on a background thread. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private void removeGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories) { + mSavePreviewsHandler.post(() -> { + SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider); + boolean changed = false; + for (int flag : Provider.WIDGET_CATEGORY_FLAGS) { + if ((widgetCategories & flag) != 0) { + changed |= previews.removeReturnOld(flag) != null; + } + } + if (changed) { + saveGeneratedPreviews(provider, previews, /* notify= */ true); + } + }); + } + + /** + * Clear previews for the specified provider on a background thread. Returns true if changed + * (i.e. there are previews to clear). If returns true, the caller should schedule a providers + * changed notification. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private boolean clearGeneratedPreviewsAsync(@NonNull Provider provider) { + mSavePreviewsHandler.post(() -> { + saveGeneratedPreviews(provider, /* previews= */ null, /* notify= */ false); + }); + return provider.info.generatedPreviewCategories != 0; + } + + private void checkSavePreviewsThread() { + if (DEBUG && !mSavePreviewsHandler.getLooper().isCurrentThread()) { + throw new IllegalStateException("Only modify previews on the background thread"); + } + } + + /** + * Load previews from file for the given provider. If there are no previews, returns an empty + * SparseArray. Else, returns a SparseArray of the previews mapped by widget category. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @NonNull + private SparseArray<RemoteViews> loadGeneratedPreviews(@NonNull Provider provider) { + checkSavePreviewsThread(); + try { + AtomicFile previewsFile = getWidgetPreviewsFile(provider); + if (!previewsFile.exists()) { + return new SparseArray<>(); + } + ProtoInputStream input = new ProtoInputStream(previewsFile.readFully()); + SparseArray<RemoteViews> entries = readGeneratedPreviewsFromProto(input); + SparseArray<RemoteViews> singleCategoryKeyedEntries = new SparseArray<>(); + for (int i = 0; i < entries.size(); i++) { + int widgetCategories = entries.keyAt(i); + RemoteViews preview = entries.valueAt(i); + for (int flag : Provider.WIDGET_CATEGORY_FLAGS) { + if ((widgetCategories & flag) != 0) { + singleCategoryKeyedEntries.put(flag, preview); + } + } + } + return singleCategoryKeyedEntries; + } catch (IOException e) { + Slog.e(TAG, "Failed to load generated previews for " + provider, e); + return new SparseArray<>(); + } + } + + /** + * This is called when loading profile/group state to populate + * AppWidgetProviderInfo.generatedPreviewCategories based on what previews are saved. + * + * This is the only time previews are read while not on mSavePreviewsHandler. It happens once + * per profile during initialization, before any calls to get/set/removeWidgetPreviewAsync + * happen for that profile. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @GuardedBy("mLock") + private void loadGeneratedPreviewCategoriesLocked(int profileId) throws IOException { + for (Provider provider : mProviders) { + if (provider.id.getProfile().getIdentifier() != profileId) { + continue; + } + AtomicFile previewsFile = getWidgetPreviewsFile(provider); + if (!previewsFile.exists()) { + continue; + } + ProtoInputStream input = new ProtoInputStream(previewsFile.readFully()); + provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto( + input); + if (DEBUG) { + Slog.i(TAG, TextUtils.formatSimple( + "loadGeneratedPreviewCategoriesLocked %d %s categories %d", profileId, + provider, provider.info.generatedPreviewCategories)); + } + } + } + + /** + * Save the given previews into storage. + * + * @param provider Provider for which to save previews + * @param previews Previews to save. If null or empty, clears any saved previews for this + * provider. + * @param notify If true, then this function will notify hosts of updated provider info. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private void saveGeneratedPreviews(@NonNull Provider provider, + @Nullable SparseArray<RemoteViews> previews, boolean notify) { + checkSavePreviewsThread(); + AtomicFile file = null; + FileOutputStream stream = null; + try { + file = getWidgetPreviewsFile(provider); + if (previews == null || previews.size() == 0) { + if (file.exists()) { + if (DEBUG) { + Slog.i(TAG, "Deleting widget preview file " + file); + } + file.delete(); + } + } else { + if (DEBUG) { + Slog.i(TAG, "Writing widget preview file " + file); + } + ProtoOutputStream out = new ProtoOutputStream(); + writePreviewsToProto(out, previews); + stream = file.startWrite(); + stream.write(out.getBytes()); + file.finishWrite(stream); + } + + synchronized (mLock) { + provider.updateGeneratedPreviewCategoriesLocked(previews); + if (notify) { + scheduleNotifyGroupHostsForProvidersChangedLocked(provider.getUserId()); + } + } + } catch (IOException e) { + if (file != null && stream != null) { + file.failWrite(stream); + } + Slog.w(TAG, "Failed to save widget previews for provider " + provider.id.componentName); + } + } + + + /** + * Write the given previews as a GeneratedPreviewsProto to the output stream. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private void writePreviewsToProto(@NonNull ProtoOutputStream out, + @NonNull SparseArray<RemoteViews> generatedPreviews) { + // Collect RemoteViews mapped by hashCode in order to avoid writing duplicates. + SparseArray<Pair<Integer, RemoteViews>> previewsToWrite = new SparseArray<>(); + for (int i = 0; i < generatedPreviews.size(); i++) { + int widgetCategory = generatedPreviews.keyAt(i); + RemoteViews views = generatedPreviews.valueAt(i); + if (!previewsToWrite.contains(views.hashCode())) { + previewsToWrite.put(views.hashCode(), new Pair<>(widgetCategory, views)); + } else { + Pair<Integer, RemoteViews> entry = previewsToWrite.get(views.hashCode()); + previewsToWrite.put(views.hashCode(), + Pair.create(entry.first | widgetCategory, views)); + } + } + + for (int i = 0; i < previewsToWrite.size(); i++) { + final long token = out.start(GeneratedPreviewsProto.PREVIEWS); + Pair<Integer, RemoteViews> entry = previewsToWrite.valueAt(i); + out.write(GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES, entry.first); + final long viewsToken = out.start(GeneratedPreviewsProto.Preview.VIEWS); + entry.second.writePreviewToProto(mContext, out); + out.end(viewsToken); + out.end(token); + } + } + + /** + * Read a GeneratedPreviewsProto message from the input stream. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @NonNull + private SparseArray<RemoteViews> readGeneratedPreviewsFromProto(@NonNull ProtoInputStream input) + throws IOException { + SparseArray<RemoteViews> entries = new SparseArray<>(); + while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (input.getFieldNumber()) { + case (int) GeneratedPreviewsProto.PREVIEWS: + final long token = input.start(GeneratedPreviewsProto.PREVIEWS); + Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input, + /* skipViews= */ false); + entries.put(entry.first, entry.second); + input.end(token); + break; + default: + Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! " + + ProtoUtils.currentFieldToString(input)); + } + } + return entries; + } + + /** + * Read the widget categories from GeneratedPreviewsProto and return an int representing the + * combined widget categories of all the previews. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @AppWidgetProviderInfo.CategoryFlags + private int readGeneratedPreviewCategoriesFromProto(@NonNull ProtoInputStream input) + throws IOException { + int widgetCategories = 0; + while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (input.getFieldNumber()) { + case (int) GeneratedPreviewsProto.PREVIEWS: + final long token = input.start(GeneratedPreviewsProto.PREVIEWS); + Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input, + /* skipViews= */ true); + widgetCategories |= entry.first; + input.end(token); + break; + default: + Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! " + + ProtoUtils.currentFieldToString(input)); + } + } + return widgetCategories; + } + + /** + * Read a single GeneratedPreviewsProto.Preview message from the input stream, and returns a + * pair of widget category and corresponding RemoteViews. If skipViews is true, this function + * will only read widget categories and the returned RemoteViews will be null. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @NonNull + private Pair<Integer, RemoteViews> readSinglePreviewFromProto(@NonNull ProtoInputStream input, + boolean skipViews) throws IOException { + int widgetCategories = 0; + RemoteViews views = null; + while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (input.getFieldNumber()) { + case (int) GeneratedPreviewsProto.Preview.VIEWS: + if (skipViews) { + // ProtoInputStream will skip over the nested message when nextField() is + // called. + continue; + } + final long token = input.start(GeneratedPreviewsProto.Preview.VIEWS); + try { + views = RemoteViews.createPreviewFromProto(mContext, input); + } catch (Exception e) { + Slog.e(TAG, "Unable to deserialize RemoteViews", e); + } + input.end(token); + break; + case (int) GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES: + widgetCategories = input.readInt( + GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES); + break; + default: + Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! " + + ProtoUtils.currentFieldToString(input)); + } + } + return Pair.create(widgetCategories, views); + } + + /** + * Returns the file in which all generated previews for this provider are stored. This will be + * a path of the form: + * {@literal /data/system_ce/<userId>/appwidget/previews/<package>-<class>-<uid>.binpb} + * + * This function will not create the file if it does not already exist. + */ + @NonNull + private static AtomicFile getWidgetPreviewsFile(@NonNull Provider provider) throws IOException { + int userId = provider.getUserId(); + File previewsDirectory = getWidgetPreviewsDirectory(userId); + File providerPreviews = Environment.buildPath(previewsDirectory, + TextUtils.formatSimple("%s-%s-%d.binpb", provider.id.componentName.getPackageName(), + provider.id.componentName.getClassName(), provider.id.uid)); + return new AtomicFile(providerPreviews); + } + + /** + * Returns the widget previews directory for the given user, creating it if it does not exist. + * This will be a path of the form: + * {@literal /data/system_ce/<userId>/appwidget/previews} + */ + @NonNull + private static File getWidgetPreviewsDirectory(int userId) throws IOException { + File dataSystemCeDirectory = Environment.getDataSystemCeDirectory(userId); + File previewsDirectory = Environment.buildPath(dataSystemCeDirectory, + APPWIDGET_CE_DATA_DIRNAME, WIDGET_PREVIEWS_DIRNAME); + if (!previewsDirectory.exists()) { + if (!previewsDirectory.mkdirs()) { + throw new IOException("Unable to create widget preview directory " + + previewsDirectory.getPath()); + } + } + return previewsDirectory; + } + private static void ensureWidgetCategoryCombinationIsValid(int widgetCategories) { int validCategories = AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN | AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD @@ -5414,11 +5834,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetManager.META_DATA_APPWIDGET_PROVIDER); } if (newInfo != null) { + newInfo.generatedPreviewCategories = info.generatedPreviewCategories; info = newInfo; if (DEBUG) { Objects.requireNonNull(info); } - updateGeneratedPreviewCategoriesLocked(); } } mInfoParsed = true; @@ -5475,7 +5895,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku generatedPreviews.put(flag, preview); } } - updateGeneratedPreviewCategoriesLocked(); + updateGeneratedPreviewCategoriesLocked(generatedPreviews); } @GuardedBy("this.mLock") @@ -5487,7 +5907,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } if (changed) { - updateGeneratedPreviewCategoriesLocked(); + updateGeneratedPreviewCategoriesLocked(generatedPreviews); } return changed; } @@ -5496,17 +5916,19 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku public boolean clearGeneratedPreviewsLocked() { if (generatedPreviews.size() > 0) { generatedPreviews.clear(); - updateGeneratedPreviewCategoriesLocked(); + updateGeneratedPreviewCategoriesLocked(generatedPreviews); return true; } return false; } - @GuardedBy("this.mLock") - private void updateGeneratedPreviewCategoriesLocked() { + private void updateGeneratedPreviewCategoriesLocked( + @Nullable SparseArray<RemoteViews> previews) { info.generatedPreviewCategories = 0; - for (int i = 0; i < generatedPreviews.size(); i++) { - info.generatedPreviewCategories |= generatedPreviews.keyAt(i); + if (previews != null) { + for (int i = 0; i < previews.size(); i++) { + info.generatedPreviewCategories |= previews.keyAt(i); + } } } 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..4969ffbfa08a --- /dev/null +++ b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.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.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.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); + for (AssociationInfo a : associations) { + try { + int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); + updateAutoRevokeExemption(a.getPackageName(), uid, true); + } 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(); + } + } + } + + @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/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java index ef39846f4750..8a4b1faf2df9 100644 --- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java +++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java @@ -59,14 +59,14 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen private int mObserverCount = 0; @GuardedBy("mLock") - private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>(); + private final ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>(); /** * Mapping from camera ID to open camera app associations. Key is the camera id, value is the * information of the app's uid and package name. */ @GuardedBy("mLock") - private ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>(); + private final ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>(); static class InjectionSessionData { public int appUid; @@ -179,6 +179,15 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen Slog.w(TAG, "Unexpected close with observers remaining: " + mObserverCount); } } + // Clean up camera injection sessions (if any). + synchronized (mLock) { + for (InjectionSessionData sessionData : mPackageToSessionData.values()) { + for (CameraInjectionSession session : sessionData.cameraIdToSession.values()) { + session.close(); + } + } + mPackageToSessionData.clear(); + } mCameraManager.unregisterAvailabilityCallback(this); } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 281a2ce0556b..8b5b93e96494 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -1465,28 +1465,28 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub public int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig, @NonNull IVirtualDisplayCallback callback) { checkCallerIsDeviceOwner(); + + int displayId; + boolean showPointer; + boolean isTrustedDisplay; GenericWindowPolicyController gwpc; synchronized (mVirtualDeviceLock) { gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories()); - } - int displayId; - displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig, callback, - this, gwpc, mOwnerPackageName); - boolean isMirrorDisplay = - mDisplayManagerInternal.getDisplayIdToMirror(displayId) != Display.INVALID_DISPLAY; - gwpc.setDisplayId(displayId, isMirrorDisplay); - boolean isTrustedDisplay = - (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED) - == Display.FLAG_TRUSTED; - if (!isTrustedDisplay) { - if (getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) { - throw new SecurityException("All displays must be trusted for devices with custom" - + "clipboard policy."); + displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig, + callback, this, gwpc, mOwnerPackageName); + boolean isMirrorDisplay = + mDisplayManagerInternal.getDisplayIdToMirror(displayId) + != Display.INVALID_DISPLAY; + gwpc.setDisplayId(displayId, isMirrorDisplay); + isTrustedDisplay = + (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED) + == Display.FLAG_TRUSTED; + if (!isTrustedDisplay + && getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) { + throw new SecurityException("All displays must be trusted for devices with " + + "custom clipboard policy."); } - } - boolean showPointer; - synchronized (mVirtualDeviceLock) { if (mVirtualDisplays.contains(displayId)) { gwpc.unregisterRunningAppsChangedListener(this); throw new IllegalStateException( @@ -1523,6 +1523,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) { + if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()) { + return null; + } final long token = Binder.clearCallingIdentity(); try { PowerManager powerManager = mContext.getSystemService(PowerManager.class); @@ -1681,6 +1684,14 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return mOwnerUid; } + long getDimDurationMillis() { + return mParams.getDimDuration().toMillis(); + } + + long getScreenOffTimeoutMillis() { + return mParams.getScreenOffTimeout().toMillis(); + } + @Override // Binder call public int[] getDisplayIds() { synchronized (mVirtualDeviceLock) { diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index f87e3c338df7..6729231d68ab 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -17,6 +17,7 @@ package com.android.server.companion.virtual; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; +import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS; import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE; import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID; @@ -27,6 +28,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.app.ActivityOptions; +import android.app.compat.CompatChanges; import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; @@ -41,11 +43,14 @@ import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.flags.Flags; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtualnative.IVirtualDeviceManagerNative; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.AttributionSource; import android.content.Context; import android.content.Intent; import android.hardware.display.DisplayManagerInternal; import android.os.Binder; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.LocaleList; @@ -88,7 +93,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.stream.Collectors; - @SuppressLint("LongLogTag") public class VirtualDeviceManagerService extends SystemService { @@ -101,6 +105,11 @@ public class VirtualDeviceManagerService extends SystemService { AssociationRequest.DEVICE_PROFILE_APP_STREAMING, AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING); + /** Enable default device camera access for apps running on virtual devices. */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + public static final long ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS = 371173368L; + /** * A virtual device association id corresponding to no CDM association. */ @@ -110,7 +119,7 @@ public class VirtualDeviceManagerService extends SystemService { private final VirtualDeviceManagerImpl mImpl; private final VirtualDeviceManagerNativeImpl mNativeImpl; private final VirtualDeviceManagerInternal mLocalService; - private VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext()); + private final VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext()); private final Handler mHandler = new Handler(Looper.getMainLooper()); private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler); @@ -236,7 +245,7 @@ public class VirtualDeviceManagerService extends SystemService { } } - void onCameraAccessBlocked(int appUid) { + private void onCameraAccessBlocked(int appUid) { ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot(); for (int i = 0; i < virtualDevicesSnapshot.size(); i++) { VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i); @@ -248,8 +257,13 @@ public class VirtualDeviceManagerService extends SystemService { } } - CameraAccessController getCameraAccessController(UserHandle userHandle) { - if (Flags.streamCamera()) { + private CameraAccessController getCameraAccessController(UserHandle userHandle, + VirtualDeviceParams params, String callingPackage) { + if (CompatChanges.isChangeEnabled(ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS, callingPackage, + userHandle) + && android.companion.virtualdevice.flags.Flags.defaultDeviceCameraAccessPolicy() + && (params.getDevicePolicy(POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS) + == DEVICE_POLICY_DEFAULT)) { return null; } int userId = userHandle.getIdentifier(); @@ -496,7 +510,8 @@ public class VirtualDeviceManagerService extends SystemService { final UserHandle userHandle = getCallingUserHandle(); final CameraAccessController cameraAccessController = - getCameraAccessController(userHandle); + getCameraAccessController(userHandle, params, + attributionSource.getPackageName()); final int deviceId = sNextUniqueIndex.getAndIncrement(); final Consumer<ArraySet<Integer>> runningAppsChangedCallback = runningUids -> notifyRunningAppsChanged(deviceId, runningUids); @@ -576,7 +591,6 @@ public class VirtualDeviceManagerService extends SystemService { } } - @Override // Binder call public int getDeviceIdForDisplayId(int displayId) { if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) { @@ -911,6 +925,22 @@ public class VirtualDeviceManagerService extends SystemService { } @Override + public long getDimDurationMillisForDeviceId(int deviceId) { + synchronized (mVirtualDeviceManagerLock) { + VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId); + return virtualDevice == null ? -1 : virtualDevice.getDimDurationMillis(); + } + } + + @Override + public long getScreenOffTimeoutMillisForDeviceId(int deviceId) { + synchronized (mVirtualDeviceManagerLock) { + VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId); + return virtualDevice == null ? -1 : virtualDevice.getScreenOffTimeoutMillis(); + } + } + + @Override public boolean isValidVirtualDeviceId(int deviceId) { return mImpl.isValidVirtualDeviceId(deviceId); } diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java index dbf144f0c63e..95ae11e28218 100644 --- a/services/core/java/com/android/server/SerialService.java +++ b/services/core/java/com/android/server/SerialService.java @@ -18,22 +18,30 @@ package com.android.server; import android.annotation.EnforcePermission; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.hardware.ISerialManager; import android.hardware.SerialManagerInternal; import android.os.ParcelFileDescriptor; import android.os.PermissionEnforcer; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import java.io.File; +import java.io.FileDescriptor; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.function.Supplier; @android.ravenwood.annotation.RavenwoodKeepWholeClass public class SerialService extends ISerialManager.Stub { + private static final String TAG = "SerialService"; + private final Context mContext; @GuardedBy("mSerialPorts") @@ -50,7 +58,7 @@ public class SerialService extends ISerialManager.Stub { final String[] serialPorts = getSerialPorts(context); for (String serialPort : serialPorts) { mSerialPorts.put(serialPort, () -> { - return native_open(serialPort); + return tryOpen(serialPort); }); } } @@ -130,5 +138,14 @@ public class SerialService extends ISerialManager.Stub { } }; - private native ParcelFileDescriptor native_open(String path); + private static @Nullable ParcelFileDescriptor tryOpen(String path) { + try { + FileDescriptor fd = Os.open(path, OsConstants.O_RDWR | OsConstants.O_NOCTTY, 0); + return new ParcelFileDescriptor(fd); + } catch (ErrnoException e) { + Slog.e(TAG, "Could not open: " + path, e); + // We return null to preserve API semantics from earlier implementation variants. + return null; + } + } } diff --git a/services/core/java/com/android/server/TradeInModeService.java b/services/core/java/com/android/server/TradeInModeService.java index 9ad550b6caf9..70a033086261 100644 --- a/services/core/java/com/android/server/TradeInModeService.java +++ b/services/core/java/com/android/server/TradeInModeService.java @@ -110,6 +110,8 @@ public final class TradeInModeService extends SystemService { stopTradeInMode(); } else { watchForSetupCompletion(); + watchForNetworkChange(); + watchForAccountsCreated(); } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 1c3569dd52d0..3e7bcb81c47f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5155,6 +5155,11 @@ public class ActivityManagerService extends IActivityManager.Stub mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action == null) { + return; + } + String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); if (pkgs != null) { for (String pkg : pkgs) { diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index de3e2c90efe1..08632fe09b19 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -114,6 +114,7 @@ import static com.android.server.am.ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ; import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ; import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ; +import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ; import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND; import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT; import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW; @@ -696,7 +697,7 @@ public class OomAdjuster { // In case the app goes from non-cached to cached but it doesn't have other reachable // processes, its adj could be still unknown as of now, assign one. processes.add(app); - assignCachedAdjIfNecessary(processes); + applyLruAdjust(processes); applyOomAdjLSP(app, false, mInjector.getUptimeMillis(), mInjector.getElapsedRealtimeMillis(), oomAdjReason); } @@ -1086,7 +1087,7 @@ public class OomAdjuster { } mProcessesInCycle.clear(); - assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); + applyLruAdjust(mProcessList.getLruProcessesLOSP()); postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true); @@ -1148,8 +1149,9 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) { + protected void applyLruAdjust(ArrayList<ProcessRecord> lruList) { final int numLru = lruList.size(); + int nextPreviousAppAdj = PREVIOUS_APP_ADJ; if (mConstants.USE_TIERED_CACHED_ADJ) { final long now = mInjector.getUptimeMillis(); int uiTargetAdj = 10; @@ -1159,9 +1161,12 @@ public class OomAdjuster { ProcessRecord app = lruList.get(i); final ProcessStateRecord state = app.mState; final ProcessCachedOptimizerRecord opt = app.mOptRecord; - if (!app.isKilledByAm() && app.getThread() != null - && (state.getCurAdj() >= UNKNOWN_ADJ - || (state.hasShownUi() && state.getCurAdj() >= CACHED_APP_MIN_ADJ))) { + final int curAdj = state.getCurAdj(); + if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) { + state.setCurAdj(nextPreviousAppAdj); + nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ); + } else if (!app.isKilledByAm() && app.getThread() != null && (curAdj >= UNKNOWN_ADJ + || (state.hasShownUi() && curAdj >= CACHED_APP_MIN_ADJ))) { final ProcessServiceRecord psr = app.mServices; int targetAdj = CACHED_APP_MIN_ADJ; @@ -1228,10 +1233,13 @@ public class OomAdjuster { for (int i = numLru - 1; i >= 0; i--) { ProcessRecord app = lruList.get(i); final ProcessStateRecord state = app.mState; - // If we haven't yet assigned the final cached adj - // to the process, do that now. - if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj() - >= UNKNOWN_ADJ) { + final int curAdj = state.getCurAdj(); + if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) { + state.setCurAdj(nextPreviousAppAdj); + nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ); + } else if (!app.isKilledByAm() && app.getThread() != null + && curAdj >= UNKNOWN_ADJ) { + // If we haven't yet assigned the final cached adj to the process, do that now. final ProcessServiceRecord psr = app.mServices; switch (state.getCurProcState()) { case PROCESS_STATE_LAST_ACTIVITY: diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index e452c45156c9..8b660559f550 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -54,6 +54,7 @@ import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_AP import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ; import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ; +import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ; import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND; import static com.android.server.am.ProcessList.SERVICE_ADJ; import static com.android.server.am.ProcessList.SERVICE_B_ADJ; @@ -968,7 +969,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, null, true); computeConnectionsLSP(); - assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); + applyLruAdjust(mProcessList.getLruProcessesLOSP()); postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime, true); } @@ -1049,20 +1050,24 @@ public class OomAdjusterModernImpl extends OomAdjuster { // Now traverse and compute the connections of processes with changed importance. computeConnectionsLSP(); - boolean unassignedAdj = false; + boolean needLruAdjust = false; for (int i = 0, size = reachables.size(); i < size; i++) { final ProcessStateRecord state = reachables.get(i).mState; state.setReachable(false); state.setCompletedAdjSeq(mAdjSeq); - if (state.getCurAdj() >= UNKNOWN_ADJ) { - unassignedAdj = true; + final int curAdj = state.getCurAdj(); + // Processes assigned the PREV oomscore will have a laddered oomscore with respect to + // their positions in the LRU list. i.e. prev+0, prev+1, prev+2, etc. + final boolean isPrevApp = PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ; + if (curAdj >= UNKNOWN_ADJ || (Flags.oomadjusterPrevLaddering() && isPrevApp)) { + needLruAdjust = true; } } // If all processes have an assigned adj, no need to calculate and assign cached adjs. - if (unassignedAdj) { + if (needLruAdjust) { // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list. - assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); + applyLruAdjust(mProcessList.getLruProcessesLOSP()); } // Repopulate any uid record that may have changed. diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index cdb01889c139..f86474f0dcaf 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -225,6 +225,7 @@ public final class ProcessList { // UI flow such as clicking on a URI in the e-mail app to view in the browser, // and then pressing back to return to e-mail. public static final int PREVIOUS_APP_ADJ = 700; + public static final int PREVIOUS_APP_MAX_ADJ = Flags.oomadjusterPrevLaddering() ? 799 : 700; // This is a process holding the home application -- we want to try // avoiding killing it, even if it would normally be in the background, diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 56cfdfb7edde..7b4d6c7fff82 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -242,4 +242,12 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "oomadjuster_prev_laddering" + namespace: "system_performance" + is_fixed_read_only: true + description: "Add +X to the prev scores according to their positions in the process LRU list" + bug: "359912586" }
\ No newline at end of file diff --git a/services/core/java/com/android/server/appbinding/AppBindingService.java b/services/core/java/com/android/server/appbinding/AppBindingService.java index 5db6dc7ccc15..6ccb3ee8bcc9 100644 --- a/services/core/java/com/android/server/appbinding/AppBindingService.java +++ b/services/core/java/com/android/server/appbinding/AppBindingService.java @@ -235,6 +235,9 @@ public class AppBindingService extends Binder { } final String action = intent.getAction(); + if (action == null) { + return; + } if (Intent.ACTION_USER_REMOVED.equals(action)) { onUserRemoved(userId); 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/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 985155d0d891..0cf55bb2bf17 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -493,7 +493,7 @@ public class AudioService extends IAudioService.Stub private static final int MSG_INIT_ADI_DEVICE_STATES = 103; private static final int MSG_INIT_INPUT_GAINS = 104; - private static final int MSG_SET_INPUT_GAIN_INDEX = 105; + private static final int MSG_APPLY_INPUT_GAIN_INDEX = 105; private static final int MSG_PERSIST_INPUT_GAIN_INDEX = 106; // end of messages handled under wakelock @@ -1626,7 +1626,6 @@ public class AudioService extends IAudioService.Stub new InputDeviceVolumeHelper( mSettings, mContentResolver, - mSettingsLock, System.INPUT_GAIN_INDEX_SETTINGS); } @@ -5804,7 +5803,7 @@ public class AudioService extends IAudioService.Stub // to persist). sendMsg( mAudioHandler, - MSG_SET_INPUT_GAIN_INDEX, + MSG_APPLY_INPUT_GAIN_INDEX, SENDMSG_QUEUE, /*arg1*/ index, /*arg2*/ 0, @@ -5813,22 +5812,22 @@ public class AudioService extends IAudioService.Stub } } - private void setInputGainIndexInt(@NonNull AudioDeviceAttributes ada, int index) { + private void onApplyInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) { // TODO(b/364923030): call AudioSystem to apply input gain in native layer. // Post a persist input gain msg. sendMsg( mAudioHandler, MSG_PERSIST_INPUT_GAIN_INDEX, - SENDMSG_QUEUE, - /*arg1*/ index, + SENDMSG_REPLACE, + /*arg1*/ 0, /*arg2*/ 0, /*obj*/ ada, PERSIST_DELAY); } - private void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) { - mInputDeviceVolumeHelper.persistInputGainIndex(ada, index); + private void onPersistInputGainIndex(@NonNull AudioDeviceAttributes ada) { + mInputDeviceVolumeHelper.persistInputGainIndex(ada); } /** @@ -10213,12 +10212,12 @@ public class AudioService extends IAudioService.Stub vgs.persistVolumeGroup(msg.arg1); break; - case MSG_SET_INPUT_GAIN_INDEX: - setInputGainIndexInt((AudioDeviceAttributes) msg.obj, msg.arg1); + case MSG_APPLY_INPUT_GAIN_INDEX: + onApplyInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1); break; case MSG_PERSIST_INPUT_GAIN_INDEX: - persistInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1); + onPersistInputGainIndex((AudioDeviceAttributes) msg.obj); break; case MSG_PERSIST_RINGER_MODE: diff --git a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java index d83dca629d74..d094629cc57b 100644 --- a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java +++ b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 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. @@ -43,7 +43,6 @@ import java.util.Set; private final SettingsAdapter mSettings; private final ContentResolver mContentResolver; - private final Object mSettingsLock; private final String mInputGainIndexSettingsName; // A map between device internal type (e.g. AudioSystem.DEVICE_IN_BUILTIN_MIC) to its input gain @@ -54,11 +53,9 @@ import java.util.Set; InputDeviceVolumeHelper( SettingsAdapter settings, ContentResolver contentResolver, - Object settingsLock, String settingsName) { mSettings = settings; mContentResolver = contentResolver; - mSettingsLock = settingsLock; mInputGainIndexSettingsName = settingsName; IntArray internalDeviceTypes = new IntArray(); @@ -82,34 +79,27 @@ import java.util.Set; readSettings(); } - public void readSettings() { + private void readSettings() { synchronized (InputDeviceVolumeHelper.class) { for (int inputDeviceType : mSupportedDeviceTypes) { // Retrieve current input gain for device. If no input gain stored for current // device, use default input gain. - int index; - if (!hasValidSettingsName()) { - index = INDEX_DEFAULT; - } else { - String name = getSettingNameForDevice(inputDeviceType); - index = - mSettings.getSystemIntForUser( - mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT); - } + String name = getSettingNameForDevice(inputDeviceType); + int index = name == null + ? INDEX_DEFAULT + : mSettings.getSystemIntForUser( + mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT); mInputGainIndexMap.put(inputDeviceType, getValidIndex(index)); } } } - public boolean hasValidSettingsName() { - return mInputGainIndexSettingsName != null && !mInputGainIndexSettingsName.isEmpty(); - } - - public @Nullable String getSettingNameForDevice(int inputDeviceType) { - if (!hasValidSettingsName()) { + private @Nullable String getSettingNameForDevice(int inputDeviceType) { + if (mInputGainIndexSettingsName == null || mInputGainIndexSettingsName.isEmpty()) { return null; } + final String suffix = AudioSystem.getInputDeviceName(inputDeviceType); if (suffix.isEmpty()) { return mInputGainIndexSettingsName; @@ -158,29 +148,27 @@ import java.util.Set; ensureValidInputDeviceType(inputDeviceType); int oldIndex; - synchronized (mSettingsLock) { - synchronized (InputDeviceVolumeHelper.class) { - oldIndex = getInputGainIndex(ada); - index = getValidIndex(index); - - if (oldIndex == index) { - return false; - } + synchronized (InputDeviceVolumeHelper.class) { + oldIndex = getInputGainIndex(ada); + index = getValidIndex(index); - mInputGainIndexMap.put(inputDeviceType, index); - return true; + if (oldIndex == index) { + return false; } + + mInputGainIndexMap.put(inputDeviceType, index); + return true; } } - public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) { + public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada) { int inputDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(ada.getType()); - ensureValidInputDeviceType(inputDeviceType); - - if (hasValidSettingsName()) { + String name = getSettingNameForDevice(inputDeviceType); + if (name != null) { + int index = getInputGainIndex(ada); mSettings.putSystemIntForUser( mContentResolver, - getSettingNameForDevice(inputDeviceType), + name, index, UserHandle.USER_CURRENT); } diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index 6e38733f04c2..471b7b4ddfc8 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -167,6 +167,12 @@ public abstract class VirtualDeviceManagerInternal { */ public abstract int getDeviceIdForDisplayId(int displayId); + /** Returns the dim duration for the displays of the device with the given ID. */ + public abstract long getDimDurationMillisForDeviceId(int deviceId); + + /** Returns the screen off timeout of the displays of the device with the given ID. */ + public abstract long getScreenOffTimeoutMillisForDeviceId(int deviceId); + /** * Gets the persistent ID for the VirtualDevice with the given device ID. * 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/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 19305dedcb06..76e5ef011789 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -605,7 +605,7 @@ public final class DreamManagerService extends SystemService { private ComponentName chooseDreamForUser(boolean doze, int userId) { if (doze) { ComponentName dozeComponent = getDozeComponent(userId); - return validateDream(dozeComponent) ? dozeComponent : null; + return validateDream(dozeComponent, userId) ? dozeComponent : null; } if (mSystemDreamComponent != null) { @@ -616,11 +616,11 @@ public final class DreamManagerService extends SystemService { return dreams != null && dreams.length != 0 ? dreams[0] : null; } - private boolean validateDream(ComponentName component) { + private boolean validateDream(ComponentName component, int userId) { if (component == null) return false; - final ServiceInfo serviceInfo = getServiceInfo(component); + final ServiceInfo serviceInfo = getServiceInfo(component, userId); if (serviceInfo == null) { - Slog.w(TAG, "Dream " + component + " does not exist"); + Slog.w(TAG, "Dream " + component + " does not exist on user " + userId); return false; } else if (serviceInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP && !BIND_DREAM_SERVICE.equals(serviceInfo.permission)) { @@ -647,7 +647,7 @@ public final class DreamManagerService extends SystemService { List<ComponentName> validComponents = new ArrayList<>(); if (components != null) { for (ComponentName component : components) { - if (validateDream(component)) { + if (validateDream(component, userId)) { validComponents.add(component); } } @@ -718,9 +718,10 @@ public final class DreamManagerService extends SystemService { return userId == mainUserId; } - private ServiceInfo getServiceInfo(ComponentName name) { + private ServiceInfo getServiceInfo(ComponentName name, int userId) { + final Context userContext = mContext.createContextAsUser(UserHandle.of(userId), 0); try { - return name != null ? mContext.getPackageManager().getServiceInfo(name, + return name != null ? userContext.getPackageManager().getServiceInfo(name, PackageManager.MATCH_DEBUG_TRIAGED_MISSING) : null; } catch (NameNotFoundException e) { return null; @@ -813,7 +814,7 @@ public final class DreamManagerService extends SystemService { private void writePulseGestureEnabled() { ComponentName name = getDozeComponent(); - boolean dozeEnabled = validateDream(name); + boolean dozeEnabled = validateDream(name, ActivityManager.getCurrentUser()); LocalServices.getService(InputManagerInternal.class).setPulseGestureEnabled(dozeEnabled); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index b696c5481205..1b527daafd24 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -273,13 +273,8 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { private class DelayedStandbyOnActiveSourceLostRunnable implements Runnable { @Override public void run() { - if (mService.getPowerManagerInternal().wasDeviceIdleFor( - STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS)) { + if (!isActiveSource()) { mService.standby(); - } else { - mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(), - getDeviceInfo().getDeviceType(), Constants.ADDR_TV, - "DelayedActiveSourceLostStandbyRunnable"); } } } diff --git a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java new file mode 100644 index 000000000000..aef207f9c027 --- /dev/null +++ b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java @@ -0,0 +1,336 @@ +/* + * 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. + */ + +package com.android.server.input; + +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.app.role.RoleManager; +import android.content.Context; +import android.content.Intent; +import android.content.res.XmlResourceParser; +import android.hardware.input.AppLaunchData; +import android.hardware.input.InputGestureData; +import android.hardware.input.KeyGestureEvent; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.IndentingPrintWriter; +import android.util.Log; +import android.util.LongSparseArray; +import android.util.SparseArray; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; + +import com.android.internal.R; +import com.android.internal.policy.IShortcutService; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Manages quick launch app shortcuts by parsing {@code bookmarks.xml} and intercepting the + * correct key combinations for the app shortcuts defined. + * + * Currently there are 2 ways of defining shortcuts: + * - Adding shortcuts to {@code bookmarks.xml} + * - Calling into {@code registerShortcutKey()}. + */ +final class AppLaunchShortcutManager { + private static final String TAG = "AppShortcutManager"; + + private static final String TAG_BOOKMARKS = "bookmarks"; + private static final String TAG_BOOKMARK = "bookmark"; + + private static final String ATTRIBUTE_PACKAGE = "package"; + private static final String ATTRIBUTE_CLASS = "class"; + private static final String ATTRIBUTE_SHORTCUT = "shortcut"; + private static final String ATTRIBUTE_CATEGORY = "category"; + private static final String ATTRIBUTE_SHIFT = "shift"; + private static final String ATTRIBUTE_ROLE = "role"; + + private static final int SHORTCUT_CODE_META_MASK = + KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON + | KeyEvent.META_META_ON; + + private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>(); + + /* Table of Application Launch keys. Maps from key codes to intent categories. + * + * These are special keys that are used to launch particular kinds of applications, + * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C) + * usage page. We don't support quite that many yet... + */ + private static final SparseArray<String> sApplicationLaunchKeyCategories; + private static final SparseArray<String> sApplicationLaunchKeyRoles; + static { + sApplicationLaunchKeyRoles = new SparseArray<>(); + sApplicationLaunchKeyCategories = new SparseArray<>(); + sApplicationLaunchKeyRoles.append( + KeyEvent.KEYCODE_EXPLORER, RoleManager.ROLE_BROWSER); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR); + } + + private final Context mContext; + private boolean mSearchKeyShortcutPending = false; + private boolean mConsumeSearchKeyUp = true; + private final Map<InputGestureData.Trigger, InputGestureData> mBookmarks = new HashMap<>(); + + @SuppressLint("MissingPermission") + AppLaunchShortcutManager(Context context) { + mContext = context; + } + + public void systemRunning() { + loadShortcuts(); + } + + private void loadShortcuts() { + try { + XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks); + XmlUtils.beginDocument(parser, TAG_BOOKMARKS); + KeyCharacterMap virtualKcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); + + while (true) { + XmlUtils.nextElement(parser); + + if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { + break; + } + + if (!TAG_BOOKMARK.equals(parser.getName())) { + Log.w(TAG, "TAG_BOOKMARK not found"); + break; + } + + String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE); + String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS); + String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY); + String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT); + String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE); + + // TODO(b/358569822): Shift bookmarks to use keycode instead of shortcutChar + int keycode = KeyEvent.KEYCODE_UNKNOWN; + String shortcut = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT); + if (!TextUtils.isEmpty(shortcut)) { + KeyEvent[] events = virtualKcm.getEvents(new char[]{shortcut.toLowerCase( + Locale.ROOT).charAt(0)}); + // Single key press can generate the character + if (events != null && events.length == 2) { + keycode = events[0].getKeyCode(); + } + } + if (keycode == KeyEvent.KEYCODE_UNKNOWN) { + Log.w(TAG, "Keycode required for bookmark with category=" + categoryName + + " packageName=" + packageName + " className=" + className + + " role=" + roleName + " shiftName=" + shiftName + + " shortcut=" + shortcut); + continue; + } + + final boolean isShiftShortcut = (shiftName != null && shiftName.toLowerCase( + Locale.ROOT).equals("true")); + AppLaunchData launchData = null; + if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) { + launchData = AppLaunchData.createLaunchDataForComponent(packageName, className); + } else if (!TextUtils.isEmpty(categoryName)) { + launchData = AppLaunchData.createLaunchDataForCategory(categoryName); + } else if (!TextUtils.isEmpty(roleName)) { + launchData = AppLaunchData.createLaunchDataForRole(roleName); + } + if (launchData != null) { + Log.d(TAG, "adding shortcut " + launchData + "shift=" + + isShiftShortcut + " keycode=" + keycode); + // All bookmarks are based on Action key + int modifierState = + KeyEvent.META_META_ON | (isShiftShortcut ? KeyEvent.META_SHIFT_ON : 0); + InputGestureData bookmark = new InputGestureData.Builder() + .setTrigger(InputGestureData.createKeyTrigger(keycode, modifierState)) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION) + .setAppLaunchData(launchData) + .build(); + mBookmarks.put(bookmark.getTrigger(), bookmark); + } + } + } catch (XmlPullParserException | IOException e) { + Log.e(TAG, "Got exception parsing bookmarks.", e); + } + } + + public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService) + throws RemoteException { + IShortcutService service = mShortcutKeyServices.get(shortcutCode); + if (service != null && service.asBinder().pingBinder()) { + throw new RemoteException("Key: " + shortcutCode + ", already exists."); + } + + mShortcutKeyServices.put(shortcutCode, shortcutService); + } + + /** + * Handle the shortcut to {@link IShortcutService} + * @param keyCode The key code of the event. + * @param metaState The meta key modifier state. + * @return True if invoked the shortcut, otherwise false. + */ + private boolean handleShortcutService(int keyCode, int metaState) { + final long shortcutCodeMeta = metaState & SHORTCUT_CODE_META_MASK; + if (shortcutCodeMeta == 0) { + return false; + } + long shortcutCode = keyCode | (shortcutCodeMeta << Integer.SIZE); + IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode); + if (shortcutService != null) { + try { + shortcutService.notifyShortcutKeyPressed(shortcutCode); + } catch (RemoteException e) { + Log.w(TAG, + "Shortcut key service not found, deleting shortcut code: " + shortcutCode); + mShortcutKeyServices.delete(shortcutCode); + } + return true; + } + return false; + } + + /** + * Handle the shortcut to Launch application. + * + * @param keyEvent The key event. + */ + @SuppressLint("MissingPermission") + @Nullable + private AppLaunchData interceptShortcut(KeyEvent keyEvent) { + final int keyCode = keyEvent.getKeyCode(); + final int modifierState = keyEvent.getMetaState() & SHORTCUT_CODE_META_MASK; + // Shortcuts are invoked through Search+key, so intercept those here + // Any printing key that is chorded with Search should be consumed + // even if no shortcut was invoked. This prevents text from being + // inadvertently inserted when using a keyboard that has built-in macro + // shortcut keys (that emit Search+x) and some of them are not registered. + if (mSearchKeyShortcutPending) { + KeyCharacterMap kcm = keyEvent.getKeyCharacterMap(); + if (kcm != null && kcm.isPrintingKey(keyCode)) { + mConsumeSearchKeyUp = true; + mSearchKeyShortcutPending = false; + } else { + return null; + } + } else if (modifierState == 0) { + AppLaunchData appLaunchData = null; + // Handle application launch keys. + String role = sApplicationLaunchKeyRoles.get(keyCode); + String category = sApplicationLaunchKeyCategories.get(keyCode); + if (!TextUtils.isEmpty(role)) { + appLaunchData = AppLaunchData.createLaunchDataForRole(role); + } else if (!TextUtils.isEmpty(category)) { + appLaunchData = AppLaunchData.createLaunchDataForCategory(category); + } + + return appLaunchData; + } + + if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { + return null; + } + InputGestureData gesture = mBookmarks.get( + InputGestureData.createKeyTrigger(keyCode, modifierState)); + if (gesture == null) { + return null; + } + return gesture.getAction().appLaunchData(); + } + + /** + * Handle the shortcut from {@link KeyEvent} + * + * @param event Description of the key event. + */ + public InterceptKeyResult interceptKey(KeyEvent event) { + if (event.getRepeatCount() != 0) { + return InterceptKeyResult.DO_NOTHING; + } + + final int metaState = event.getModifiers(); + final int keyCode = event.getKeyCode(); + if (keyCode == KeyEvent.KEYCODE_SEARCH) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + mSearchKeyShortcutPending = true; + mConsumeSearchKeyUp = false; + } else { + mSearchKeyShortcutPending = false; + if (mConsumeSearchKeyUp) { + mConsumeSearchKeyUp = false; + return InterceptKeyResult.CONSUME_KEY; + } + } + return InterceptKeyResult.DO_NOTHING; + } + + if (event.getAction() != KeyEvent.ACTION_DOWN) { + return InterceptKeyResult.DO_NOTHING; + } + + // Intercept shortcuts defined in bookmarks or through application launch keycodes + AppLaunchData appLaunchData = interceptShortcut(event); + + // TODO(b/358569822): Ideally shortcut service custom shortcuts should be either + // migrated to bookmarks or customizable shortcut APIs. + if (appLaunchData == null && handleShortcutService(keyCode, metaState)) { + return InterceptKeyResult.CONSUME_KEY; + } + + return new InterceptKeyResult(/* consumed =*/ false, appLaunchData); + } + + /** + * @return a list of {@link InputGestureData} containing the application launch shortcuts parsed + * at boot time from {@code bookmarks.xml}. + */ + public List<InputGestureData> getBookmarks() { + return new ArrayList<>(mBookmarks.values()); + } + + public void dump(IndentingPrintWriter ipw) { + ipw.println("AppLaunchShortcutManager:"); + ipw.increaseIndent(); + for (InputGestureData data : mBookmarks.values()) { + ipw.println(data); + } + ipw.decreaseIndent(); + } + + public record InterceptKeyResult(boolean consumed, @Nullable AppLaunchData appLaunchData) { + private static final InterceptKeyResult DO_NOTHING = new InterceptKeyResult(false, null); + private static final InterceptKeyResult CONSUME_KEY = new InterceptKeyResult(true, null); + } +} diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java index f4bd402e63a2..cf1cdaf55e5c 100644 --- a/services/core/java/com/android/server/input/InputGestureManager.java +++ b/services/core/java/com/android/server/input/InputGestureManager.java @@ -16,11 +16,21 @@ package com.android.server.input; +import static android.hardware.input.InputGestureData.createKeyTrigger; +import static com.android.hardware.input.Flags.keyboardA11yShortcutControl; +import static com.android.server.flags.Flags.newBugreportKeyboardShortcut; +import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut; +import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.content.Context; import android.hardware.input.InputGestureData; import android.hardware.input.InputManager; +import android.hardware.input.InputSettings; +import android.hardware.input.KeyGestureEvent; +import android.os.SystemProperties; import android.util.IndentingPrintWriter; import android.util.SparseArray; import android.view.KeyEvent; @@ -29,15 +39,17 @@ import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; /** * A thread-safe component of {@link InputManagerService} responsible for managing pre-defined input * gestures and custom gestures defined by other system components using Input APIs. * - * TODO(b/365064144): Add implementation to persist data, identify clashes with existing shortcuts. + * TODO(b/365064144): Add implementation to persist data. * */ final class InputGestureManager { @@ -47,13 +59,242 @@ final class InputGestureManager { KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON | KeyEvent.META_META_ON; - @GuardedBy("mCustomInputGestures") + private final Context mContext; + + private static final Object mGestureLock = new Object(); + @GuardedBy("mGestureLock") private final SparseArray<Map<InputGestureData.Trigger, InputGestureData>> mCustomInputGestures = new SparseArray<>(); + @GuardedBy("mGestureLock") + private final Map<InputGestureData.Trigger, InputGestureData> mSystemShortcuts = + new HashMap<>(); + + @GuardedBy("mGestureLock") + private final Set<InputGestureData.Trigger> mBlockListedTriggers = new HashSet<>(Set.of( + createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON), + createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON), + createKeyTrigger(KeyEvent.KEYCODE_SPACE, KeyEvent.META_CTRL_ON), + createKeyTrigger(KeyEvent.KEYCODE_SPACE, + KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON), + createKeyTrigger(KeyEvent.KEYCODE_Z, + KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON), + createKeyTrigger(KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON), + createKeyTrigger(KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_ON), + createKeyTrigger(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON), + createKeyTrigger(KeyEvent.KEYCODE_X, KeyEvent.META_CTRL_ON), + createKeyTrigger(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON), + createKeyTrigger(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON) + )); + + public InputGestureManager(Context context) { + mContext = context; + } + + public void systemRunning() { + initSystemShortcuts(); + blockListBookmarkedTriggers(); + } + + private void initSystemShortcuts() { + // Initialize all system shortcuts + List<InputGestureData> systemShortcuts = new ArrayList<>(List.of( + createKeyGesture( + KeyEvent.KEYCODE_A, + KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT + ), + createKeyGesture( + KeyEvent.KEYCODE_H, + KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_HOME + ), + createKeyGesture( + KeyEvent.KEYCODE_ENTER, + KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_HOME + ), + createKeyGesture( + KeyEvent.KEYCODE_I, + KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS + ), + createKeyGesture( + KeyEvent.KEYCODE_L, + KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN + ), + createKeyGesture( + KeyEvent.KEYCODE_N, + KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL + ), + createKeyGesture( + KeyEvent.KEYCODE_N, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES + ), + createKeyGesture( + KeyEvent.KEYCODE_S, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT + ), + createKeyGesture( + KeyEvent.KEYCODE_DEL, + KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK + ), + createKeyGesture( + KeyEvent.KEYCODE_ESCAPE, + KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK + ), + createKeyGesture( + KeyEvent.KEYCODE_DPAD_UP, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION + ), + createKeyGesture( + KeyEvent.KEYCODE_DPAD_DOWN, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE + ), + createKeyGesture( + KeyEvent.KEYCODE_DPAD_LEFT, + KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK + ), + createKeyGesture( + KeyEvent.KEYCODE_DPAD_LEFT, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT + ), + createKeyGesture( + KeyEvent.KEYCODE_DPAD_LEFT, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT + ), + createKeyGesture( + KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT + ), + createKeyGesture( + KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT + ), + createKeyGesture( + KeyEvent.KEYCODE_SLASH, + KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER + ), + createKeyGesture( + KeyEvent.KEYCODE_TAB, + KeyEvent.META_META_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS + ) + )); + if (newBugreportKeyboardShortcut() && "1".equals(SystemProperties.get("ro.debuggable"))) { + systemShortcuts.add(createKeyGesture( + KeyEvent.KEYCODE_DEL, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT + )); + } + if (enableMoveToNextDisplayShortcut()) { + systemShortcuts.add(createKeyGesture( + KeyEvent.KEYCODE_D, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY + )); + } + if (keyboardA11yShortcutControl()) { + systemShortcuts.add(createKeyGesture( + KeyEvent.KEYCODE_T, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK + )); + if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) { + systemShortcuts.add(createKeyGesture( + KeyEvent.KEYCODE_3, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS + )); + } + if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) { + systemShortcuts.add(createKeyGesture( + KeyEvent.KEYCODE_4, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS + )); + } + if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) { + systemShortcuts.add(createKeyGesture( + KeyEvent.KEYCODE_5, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS + )); + } + if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) { + systemShortcuts.add(createKeyGesture( + KeyEvent.KEYCODE_6, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS + )); + } + if (enableTaskResizingKeyboardShortcuts()) { + systemShortcuts.add(createKeyGesture( + KeyEvent.KEYCODE_LEFT_BRACKET, + KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW + )); + systemShortcuts.add(createKeyGesture( + KeyEvent.KEYCODE_RIGHT_BRACKET, + KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW + )); + systemShortcuts.add(createKeyGesture( + KeyEvent.KEYCODE_EQUALS, + KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW + )); + systemShortcuts.add(createKeyGesture( + KeyEvent.KEYCODE_MINUS, + KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE + )); + } + } + synchronized (mGestureLock) { + for (InputGestureData systemShortcut : systemShortcuts) { + mSystemShortcuts.put(systemShortcut.getTrigger(), systemShortcut); + } + } + } + + private void blockListBookmarkedTriggers() { + synchronized (mGestureLock) { + InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class)); + for (InputGestureData bookmark : im.getAppLaunchBookmarks()) { + mBlockListedTriggers.add(bookmark.getTrigger()); + } + } + } + @InputManager.CustomInputGestureResult public int addCustomInputGesture(int userId, InputGestureData newGesture) { - synchronized (mCustomInputGestures) { + synchronized (mGestureLock) { + if (mBlockListedTriggers.contains(newGesture.getTrigger()) + || mSystemShortcuts.containsKey(newGesture.getTrigger())) { + return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE; + } + if (newGesture.getTrigger() instanceof InputGestureData.KeyTrigger keyTrigger) { + if (KeyEvent.isModifierKey(keyTrigger.getKeycode()) || + KeyEvent.isSystemKey(keyTrigger.getKeycode())) { + return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE; + } + } if (!mCustomInputGestures.contains(userId)) { mCustomInputGestures.put(userId, new HashMap<>()); } @@ -69,7 +310,7 @@ final class InputGestureManager { @InputManager.CustomInputGestureResult public int removeCustomInputGesture(int userId, InputGestureData data) { - synchronized (mCustomInputGestures) { + synchronized (mGestureLock) { if (!mCustomInputGestures.contains(userId)) { return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST; } @@ -88,14 +329,14 @@ final class InputGestureManager { } public void removeAllCustomInputGestures(int userId) { - synchronized (mCustomInputGestures) { + synchronized (mGestureLock) { mCustomInputGestures.remove(userId); } } @NonNull public List<InputGestureData> getCustomInputGestures(int userId) { - synchronized (mCustomInputGestures) { + synchronized (mGestureLock) { if (!mCustomInputGestures.contains(userId)) { return List.of(); } @@ -109,7 +350,7 @@ final class InputGestureManager { if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { return null; } - synchronized (mCustomInputGestures) { + synchronized (mGestureLock) { Map<InputGestureData.Trigger, InputGestureData> customGestures = mCustomInputGestures.get(userId); if (customGestures == null) { @@ -120,10 +361,44 @@ final class InputGestureManager { } } + @Nullable + public InputGestureData getSystemShortcutForKeyEvent(KeyEvent event) { + final int keyCode = event.getKeyCode(); + if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { + return null; + } + synchronized (mGestureLock) { + int modifierState = event.getMetaState() & KEY_GESTURE_META_MASK; + return mSystemShortcuts.get(InputGestureData.createKeyTrigger(keyCode, modifierState)); + } + } + + private static InputGestureData createKeyGesture(int keycode, int modifierState, + int keyGestureType) { + return new InputGestureData.Builder() + .setTrigger(createKeyTrigger(keycode, modifierState)) + .setKeyGestureType(keyGestureType) + .build(); + } + public void dump(IndentingPrintWriter ipw) { ipw.println("InputGestureManager:"); ipw.increaseIndent(); - synchronized (mCustomInputGestures) { + synchronized (mGestureLock) { + ipw.println("System Shortcuts:"); + ipw.increaseIndent(); + for (InputGestureData systemShortcut : mSystemShortcuts.values()) { + ipw.println(systemShortcut); + } + ipw.decreaseIndent(); + ipw.println("Blocklisted Triggers:"); + ipw.increaseIndent(); + for (InputGestureData.Trigger blocklistedTrigger : mBlockListedTriggers) { + ipw.println(blocklistedTrigger); + } + ipw.decreaseIndent(); + ipw.println("Custom Gestures:"); + ipw.increaseIndent(); int size = mCustomInputGestures.size(); for (int i = 0; i < size; i++) { Map<InputGestureData.Trigger, InputGestureData> customGestures = diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java index 1c5bd59fa386..265e4531a10a 100644 --- a/services/core/java/com/android/server/input/InputManagerInternal.java +++ b/services/core/java/com/android/server/input/InputManagerInternal.java @@ -23,11 +23,13 @@ import android.graphics.PointF; import android.hardware.display.DisplayViewport; import android.hardware.input.KeyGestureEvent; import android.os.IBinder; +import android.os.RemoteException; import android.util.SparseBooleanArray; import android.view.InputChannel; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.inputmethod.InputMethodSubtypeHandle; +import com.android.internal.policy.IShortcutService; import java.util.List; @@ -278,6 +280,15 @@ public abstract class InputManagerInternal { */ public abstract void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor); + + /** + * Register shortcuts for input manager to dispatch. + * Shortcut code is packed as (metaState << Integer.SIZE) | keyCode + * @hide + */ + public abstract void registerShortcutKey(long shortcutCode, + IShortcutService shortcutKeyReceiver) throws RemoteException; + /** * Set whether the given input device can wake up the kernel from sleep * when it generates input events. By default, usually only internal (built-in) diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index eefa15e1cbaa..78e3b846f9dc 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -129,6 +129,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.InputMethodSubtypeHandle; import com.android.internal.os.SomeArgs; +import com.android.internal.policy.IShortcutService; import com.android.internal.policy.KeyInterceptionInfo; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; @@ -3025,6 +3026,11 @@ public class InputManagerService extends IInputManager.Stub return mKeyGestureController.getCustomInputGestures(UserHandle.getCallingUserId()); } + @Override + public AidlInputGestureData[] getAppLaunchBookmarks() { + return mKeyGestureController.getAppLaunchBookmarks(); + } + private void handleCurrentUserChanged(@UserIdInt int userId) { mCurrentUserId = userId; mKeyGestureController.setCurrentUserId(userId); @@ -3569,6 +3575,12 @@ public class InputManagerService extends IInputManager.Stub } @Override + public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver) + throws RemoteException { + mKeyGestureController.registerShortcutKey(shortcutCode, shortcutKeyReceiver); + } + + @Override public boolean setKernelWakeEnabled(int deviceId, boolean enabled) { return mNative.setKernelWakeEnabled(deviceId, enabled); } diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java index 96ef07004b38..e0991ec20f9e 100644 --- a/services/core/java/com/android/server/input/KeyGestureController.java +++ b/services/core/java/com/android/server/input/KeyGestureController.java @@ -20,12 +20,8 @@ import static android.content.pm.PackageManager.FEATURE_LEANBACK; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE; -import static com.android.hardware.input.Flags.keyboardA11yShortcutControl; import static com.android.hardware.input.Flags.useKeyGestureEventHandler; import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures; -import static com.android.server.flags.Flags.newBugreportKeyboardShortcut; -import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut; -import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts; import android.annotation.BinderThread; import android.annotation.MainThread; @@ -69,6 +65,7 @@ import android.view.KeyEvent; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.policy.IShortcutService; import com.android.server.policy.KeyCombinationManager; import java.util.ArrayDeque; @@ -119,7 +116,8 @@ final class KeyGestureController { private final int mSystemPid; private final KeyCombinationManager mKeyCombinationManager; private final SettingsObserver mSettingsObserver; - private final InputGestureManager mInputGestureManager = new InputGestureManager(); + private final AppLaunchShortcutManager mAppLaunchShortcutManager; + private final InputGestureManager mInputGestureManager; private static final Object mUserLock = new Object(); @UserIdInt @GuardedBy("mUserLock") @@ -131,7 +129,6 @@ final class KeyGestureController { private boolean mPendingHideRecentSwitcher; // Platform behaviors - private boolean mEnableBugReportKeyboardShortcut; private boolean mHasFeatureWatch; private boolean mHasFeatureLeanback; @@ -178,13 +175,13 @@ final class KeyGestureController { }); mKeyCombinationManager = new KeyCombinationManager(mHandler); mSettingsObserver = new SettingsObserver(mHandler); + mAppLaunchShortcutManager = new AppLaunchShortcutManager(mContext); + mInputGestureManager = new InputGestureManager(mContext); initBehaviors(); initKeyCombinationRules(); } private void initBehaviors() { - mEnableBugReportKeyboardShortcut = "1".equals(SystemProperties.get("ro.debuggable")); - PackageManager pm = mContext.getPackageManager(); mHasFeatureWatch = pm.hasSystemFeature(FEATURE_WATCH); mHasFeatureLeanback = pm.hasSystemFeature(FEATURE_LEANBACK); @@ -437,6 +434,8 @@ final class KeyGestureController { public void systemRunning() { mSettingsObserver.observe(); + mAppLaunchShortcutManager.systemRunning(); + mInputGestureManager.systemRunning(); } public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { @@ -513,15 +512,34 @@ final class KeyGestureController { mPendingCapsLockToggle = false; } + // Handle App launch shortcuts + AppLaunchShortcutManager.InterceptKeyResult result = mAppLaunchShortcutManager.interceptKey( + event); + if (result.consumed()) { + return true; + } + if (result.appLaunchData() != null) { + return handleKeyGesture(deviceId, new int[]{keyCode}, metaState, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, + focusedToken, /* flags = */0, result.appLaunchData()); + } + + // Handle system shortcuts + if (firstDown) { + InputGestureData systemShortcut = mInputGestureManager.getSystemShortcutForKeyEvent( + event); + if (systemShortcut != null) { + return handleKeyGesture(deviceId, new int[]{keyCode}, metaState, + systemShortcut.getAction().keyGestureType(), + KeyGestureEvent.ACTION_GESTURE_COMPLETE, + displayId, focusedToken, /* flags = */0, + systemShortcut.getAction().appLaunchData()); + } + } + + // Handle system keys switch (keyCode) { - case KeyEvent.KEYCODE_A: - if (firstDown && event.isMetaPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - break; case KeyEvent.KEYCODE_RECENT_APPS: if (firstDown) { handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0, @@ -544,257 +562,6 @@ final class KeyGestureController { /* appLaunchData = */null); } return true; - case KeyEvent.KEYCODE_H: - case KeyEvent.KEYCODE_ENTER: - if (firstDown && event.isMetaPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_HOME, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - break; - case KeyEvent.KEYCODE_I: - if (firstDown && event.isMetaPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - break; - case KeyEvent.KEYCODE_L: - if (firstDown && event.isMetaPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - break; - case KeyEvent.KEYCODE_N: - if (firstDown && event.isMetaPressed()) { - if (event.isCtrlPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } else { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - break; - case KeyEvent.KEYCODE_S: - if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - break; - case KeyEvent.KEYCODE_T: - if (keyboardA11yShortcutControl()) { - if (firstDown && event.isMetaPressed() && event.isAltPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - break; - case KeyEvent.KEYCODE_3: - if (InputSettings.isAccessibilityBounceKeysFeatureEnabled() - && keyboardA11yShortcutControl()) { - if (firstDown && event.isMetaPressed() && event.isAltPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - break; - case KeyEvent.KEYCODE_4: - if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled() - && keyboardA11yShortcutControl()) { - if (firstDown && event.isMetaPressed() && event.isAltPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - break; - case KeyEvent.KEYCODE_5: - if (InputSettings.isAccessibilityStickyKeysFeatureEnabled() - && keyboardA11yShortcutControl()) { - if (firstDown && event.isMetaPressed() && event.isAltPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - break; - case KeyEvent.KEYCODE_6: - if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled() - && keyboardA11yShortcutControl()) { - if (firstDown && event.isMetaPressed() && event.isAltPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - break; - case KeyEvent.KEYCODE_DEL: - if (newBugreportKeyboardShortcut()) { - if (firstDown && mEnableBugReportKeyboardShortcut && event.isMetaPressed() - && event.isCtrlPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - // fall through - case KeyEvent.KEYCODE_ESCAPE: - if (firstDown && event.isMetaPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_BACK, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - break; - case KeyEvent.KEYCODE_DPAD_UP: - if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - break; - case KeyEvent.KEYCODE_DPAD_LEFT: - if (firstDown && event.isMetaPressed()) { - if (event.isCtrlPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } else if (event.isAltPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } else { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_BACK, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (firstDown && event.isMetaPressed()) { - if (event.isCtrlPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } else if (event.isAltPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - break; - case KeyEvent.KEYCODE_D: - if (enableMoveToNextDisplayShortcut()) { - if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, - displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - break; - case KeyEvent.KEYCODE_LEFT_BRACKET: - if (enableTaskResizingKeyboardShortcuts()) { - if (firstDown && event.isAltPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, - displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - break; - case KeyEvent.KEYCODE_RIGHT_BRACKET: - if (enableTaskResizingKeyboardShortcuts()) { - if (firstDown && event.isAltPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, - displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - break; - case KeyEvent.KEYCODE_EQUALS: - if (enableTaskResizingKeyboardShortcuts()) { - if (firstDown && event.isAltPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, - displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - break; - case KeyEvent.KEYCODE_MINUS: - if (enableTaskResizingKeyboardShortcuts()) { - if (firstDown && event.isAltPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, - KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, - displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); - } - } - break; - case KeyEvent.KEYCODE_SLASH: - if (firstDown && event.isMetaPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } - break; case KeyEvent.KEYCODE_BRIGHTNESS_UP: case KeyEvent.KEYCODE_BRIGHTNESS_DOWN: if (down) { @@ -938,12 +705,7 @@ final class KeyGestureController { return true; case KeyEvent.KEYCODE_TAB: if (firstDown) { - if (event.isMetaPressed()) { - return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, - KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, - focusedToken, /* flags = */0, /* appLaunchData = */null); - } else if (!mPendingHideRecentSwitcher) { + if (!mPendingHideRecentSwitcher) { final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK; if (KeyEvent.metaStateHasModifiers( @@ -1004,6 +766,7 @@ final class KeyGestureController { return true; } + // Handle custom shortcuts if (firstDown) { InputGestureData customGesture; synchronized (mUserLock) { @@ -1258,6 +1021,16 @@ final class KeyGestureController { return result; } + @BinderThread + public AidlInputGestureData[] getAppLaunchBookmarks() { + List<InputGestureData> bookmarks = mAppLaunchShortcutManager.getBookmarks(); + AidlInputGestureData[] result = new AidlInputGestureData[bookmarks.size()]; + for (int i = 0; i < bookmarks.size(); i++) { + result[i] = bookmarks.get(i).getAidlData(); + } + return result; + } + private void onKeyGestureEventListenerDied(int pid) { synchronized (mKeyGestureEventListenerRecords) { mKeyGestureEventListenerRecords.remove(pid); @@ -1329,6 +1102,15 @@ final class KeyGestureController { } } + public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver) + throws RemoteException { + mAppLaunchShortcutManager.registerShortcutKey(shortcutCode, shortcutKeyReceiver); + } + + public List<InputGestureData> getBookmarks() { + return mAppLaunchShortcutManager.getBookmarks(); + } + private void onKeyGestureHandlerDied(int pid) { synchronized (mKeyGestureHandlerRecords) { mKeyGestureHandlerRecords.remove(pid); @@ -1471,6 +1253,7 @@ final class KeyGestureController { } ipw.decreaseIndent(); mKeyCombinationManager.dump("", ipw); + mAppLaunchShortcutManager.dump(ipw); mInputGestureManager.dump(ipw); } } 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/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 010437337ba1..d8483f721306 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -4969,7 +4969,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final var userData = getUserData(userId); if (Flags.refactorInsetsController()) { setImeVisibilityOnFocusedWindowClient(false, userData, - null /* TODO(b329229469) check statsToken */); + null /* TODO(b/353463205) check statsToken */); } else { hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 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/integrity/parser/RuleMetadataParser.java b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java deleted file mode 100644 index e831e40e70d1..000000000000 --- a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.parser; - -import android.annotation.Nullable; -import android.util.Xml; - -import com.android.modules.utils.TypedXmlPullParser; -import com.android.server.integrity.model.RuleMetadata; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.io.InputStream; - -/** Helper class for parsing rule metadata. */ -public class RuleMetadataParser { - - public static final String RULE_PROVIDER_TAG = "P"; - public static final String VERSION_TAG = "V"; - - /** Parse the rule metadata from an input stream. */ - @Nullable - public static RuleMetadata parse(InputStream inputStream) - throws XmlPullParserException, IOException { - - String ruleProvider = ""; - String version = ""; - - TypedXmlPullParser xmlPullParser = Xml.resolvePullParser(inputStream); - - int eventType; - while ((eventType = xmlPullParser.next()) != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tag = xmlPullParser.getName(); - switch (tag) { - case RULE_PROVIDER_TAG: - ruleProvider = xmlPullParser.nextText(); - break; - case VERSION_TAG: - version = xmlPullParser.nextText(); - break; - default: - throw new IllegalStateException("Unknown tag in metadata: " + tag); - } - } - } - - return new RuleMetadata(ruleProvider, version); - } -} diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java deleted file mode 100644 index 8ba5870aef0f..000000000000 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION; -import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; -import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; -import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START; -import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; -import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; -import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY; -import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE; -import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY; -import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED; - -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.InstallerAllowedByManifestFormula; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.IntegrityUtils; -import android.content.integrity.Rule; - -import com.android.internal.util.Preconditions; -import com.android.server.integrity.model.BitOutputStream; -import com.android.server.integrity.model.ByteTrackedOutputStream; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */ -public class RuleBinarySerializer implements RuleSerializer { - static final int TOTAL_RULE_SIZE_LIMIT = 200000; - static final int INDEXED_RULE_SIZE_LIMIT = 100000; - static final int NONINDEXED_RULE_SIZE_LIMIT = 1000; - - // Get the byte representation for a list of rules. - @Override - public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion) - throws RuleSerializeException { - try { - ByteArrayOutputStream rulesOutputStream = new ByteArrayOutputStream(); - serialize(rules, formatVersion, rulesOutputStream, new ByteArrayOutputStream()); - return rulesOutputStream.toByteArray(); - } catch (Exception e) { - throw new RuleSerializeException(e.getMessage(), e); - } - } - - // Get the byte representation for a list of rules, and write them to an output stream. - @Override - public void serialize( - List<Rule> rules, - Optional<Integer> formatVersion, - OutputStream rulesFileOutputStream, - OutputStream indexingFileOutputStream) - throws RuleSerializeException { - try { - if (rules == null) { - throw new IllegalArgumentException("Null rules cannot be serialized."); - } - - if (rules.size() > TOTAL_RULE_SIZE_LIMIT) { - throw new IllegalArgumentException("Too many rules provided: " + rules.size()); - } - - // Determine the indexing groups and the order of the rules within each indexed group. - Map<Integer, Map<String, List<Rule>>> indexedRules = - RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules); - - // Validate the rule blocks are not larger than expected limits. - verifySize(indexedRules.get(PACKAGE_NAME_INDEXED), INDEXED_RULE_SIZE_LIMIT); - verifySize(indexedRules.get(APP_CERTIFICATE_INDEXED), INDEXED_RULE_SIZE_LIMIT); - verifySize(indexedRules.get(NOT_INDEXED), NONINDEXED_RULE_SIZE_LIMIT); - - // Serialize the rules. - ByteTrackedOutputStream ruleFileByteTrackedOutputStream = - new ByteTrackedOutputStream(rulesFileOutputStream); - serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream); - LinkedHashMap<String, Integer> packageNameIndexes = - serializeRuleList( - indexedRules.get(PACKAGE_NAME_INDEXED), - ruleFileByteTrackedOutputStream); - LinkedHashMap<String, Integer> appCertificateIndexes = - serializeRuleList( - indexedRules.get(APP_CERTIFICATE_INDEXED), - ruleFileByteTrackedOutputStream); - LinkedHashMap<String, Integer> unindexedRulesIndexes = - serializeRuleList( - indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream); - - // Serialize their indexes. - BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream); - serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true); - serializeIndexGroup( - appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true); - serializeIndexGroup( - unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false); - indexingBitOutputStream.flush(); - } catch (Exception e) { - throw new RuleSerializeException(e.getMessage(), e); - } - } - - private void verifySize(Map<String, List<Rule>> ruleListMap, int ruleSizeLimit) { - int totalRuleCount = - ruleListMap.values().stream() - .map(list -> list.size()) - .collect(Collectors.summingInt(Integer::intValue)); - if (totalRuleCount > ruleSizeLimit) { - throw new IllegalArgumentException( - "Too many rules provided in the indexing group. Provided " - + totalRuleCount - + " limit " - + ruleSizeLimit); - } - } - - private void serializeRuleFileMetadata( - Optional<Integer> formatVersion, ByteTrackedOutputStream outputStream) - throws IOException { - int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION); - - BitOutputStream bitOutputStream = new BitOutputStream(outputStream); - bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue); - bitOutputStream.flush(); - } - - private LinkedHashMap<String, Integer> serializeRuleList( - Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream) - throws IOException { - Preconditions.checkArgument( - rulesMap != null, "serializeRuleList should never be called with null rule list."); - - BitOutputStream bitOutputStream = new BitOutputStream(outputStream); - LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap(); - indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount()); - - List<String> sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList()); - int indexTracker = 0; - for (String key : sortedKeys) { - if (indexTracker >= INDEXING_BLOCK_SIZE) { - indexMapping.put(key, outputStream.getWrittenBytesCount()); - indexTracker = 0; - } - - for (Rule rule : rulesMap.get(key)) { - serializeRule(rule, bitOutputStream); - bitOutputStream.flush(); - indexTracker++; - } - } - indexMapping.put(END_INDEXING_KEY, outputStream.getWrittenBytesCount()); - - return indexMapping; - } - - private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException { - if (rule == null) { - throw new IllegalArgumentException("Null rule can not be serialized"); - } - - // Start with a '1' bit to mark the start of a rule. - bitOutputStream.setNext(); - - serializeFormula(rule.getFormula(), bitOutputStream); - bitOutputStream.setNext(EFFECT_BITS, rule.getEffect()); - - // End with a '1' bit to mark the end of a rule. - bitOutputStream.setNext(); - } - - private void serializeFormula(IntegrityFormula formula, BitOutputStream bitOutputStream) - throws IOException { - if (formula instanceof AtomicFormula) { - serializeAtomicFormula((AtomicFormula) formula, bitOutputStream); - } else if (formula instanceof CompoundFormula) { - serializeCompoundFormula((CompoundFormula) formula, bitOutputStream); - } else if (formula instanceof InstallerAllowedByManifestFormula) { - bitOutputStream.setNext(SEPARATOR_BITS, INSTALLER_ALLOWED_BY_MANIFEST_START); - } else { - throw new IllegalArgumentException( - String.format("Invalid formula type: %s", formula.getClass())); - } - } - - private void serializeCompoundFormula( - CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException { - if (compoundFormula == null) { - throw new IllegalArgumentException("Null compound formula can not be serialized"); - } - - bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_START); - bitOutputStream.setNext(CONNECTOR_BITS, compoundFormula.getConnector()); - for (IntegrityFormula formula : compoundFormula.getFormulas()) { - serializeFormula(formula, bitOutputStream); - } - bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_END); - } - - private void serializeAtomicFormula( - AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException { - if (atomicFormula == null) { - throw new IllegalArgumentException("Null atomic formula can not be serialized"); - } - - bitOutputStream.setNext(SEPARATOR_BITS, ATOMIC_FORMULA_START); - bitOutputStream.setNext(KEY_BITS, atomicFormula.getKey()); - if (atomicFormula.getTag() == AtomicFormula.STRING_ATOMIC_FORMULA_TAG) { - AtomicFormula.StringAtomicFormula stringAtomicFormula = - (AtomicFormula.StringAtomicFormula) atomicFormula; - bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ); - serializeStringValue( - stringAtomicFormula.getValue(), - stringAtomicFormula.getIsHashedValue(), - bitOutputStream); - } else if (atomicFormula.getTag() == AtomicFormula.LONG_ATOMIC_FORMULA_TAG) { - AtomicFormula.LongAtomicFormula longAtomicFormula = - (AtomicFormula.LongAtomicFormula) atomicFormula; - bitOutputStream.setNext(OPERATOR_BITS, longAtomicFormula.getOperator()); - // TODO(b/147880712): Temporary hack until we support long values in bitOutputStream - long value = longAtomicFormula.getValue(); - serializeIntValue((int) (value >>> 32), bitOutputStream); - serializeIntValue((int) value, bitOutputStream); - } else if (atomicFormula.getTag() == AtomicFormula.BOOLEAN_ATOMIC_FORMULA_TAG) { - AtomicFormula.BooleanAtomicFormula booleanAtomicFormula = - (AtomicFormula.BooleanAtomicFormula) atomicFormula; - bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ); - serializeBooleanValue(booleanAtomicFormula.getValue(), bitOutputStream); - } else { - throw new IllegalArgumentException( - String.format("Invalid atomic formula type: %s", atomicFormula.getClass())); - } - } - - private void serializeIndexGroup( - LinkedHashMap<String, Integer> indexes, - BitOutputStream bitOutputStream, - boolean isIndexed) - throws IOException { - // Output the starting location of this indexing group. - serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream); - serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream); - - // If the group is indexed, output the locations of the indexes. - if (isIndexed) { - for (Map.Entry<String, Integer> entry : indexes.entrySet()) { - if (!entry.getKey().equals(START_INDEXING_KEY) - && !entry.getKey().equals(END_INDEXING_KEY)) { - serializeStringValue( - entry.getKey(), /* isHashedValue= */ false, bitOutputStream); - serializeIntValue(entry.getValue(), bitOutputStream); - } - } - } - - // Output the end location of this indexing group. - serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream); - serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream); - } - - private void serializeStringValue( - String value, boolean isHashedValue, BitOutputStream bitOutputStream) - throws IOException { - if (value == null) { - throw new IllegalArgumentException("String value can not be null."); - } - byte[] valueBytes = getBytesForString(value, isHashedValue); - - bitOutputStream.setNext(isHashedValue); - bitOutputStream.setNext(VALUE_SIZE_BITS, valueBytes.length); - for (byte valueByte : valueBytes) { - bitOutputStream.setNext(/* numOfBits= */ 8, valueByte); - } - } - - private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException { - bitOutputStream.setNext(/* numOfBits= */ 32, value); - } - - private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream) - throws IOException { - bitOutputStream.setNext(value); - } - - // Get the byte array for a value. - // If the value is not hashed, use its byte array form directly. - // If the value is hashed, get the raw form decoding of the value. All hashed values are - // hex-encoded. Serialized values are in raw form. - private static byte[] getBytesForString(String value, boolean isHashedValue) { - if (!isHashedValue) { - return value.getBytes(StandardCharsets.UTF_8); - } - return IntegrityUtils.getBytesFromHexDigest(value); - } -} diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java deleted file mode 100644 index 2cbd4ede5214..000000000000 --- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** Holds the indexing type and indexing key of a given formula. */ -class RuleIndexingDetails { - - static final int NOT_INDEXED = 0; - static final int PACKAGE_NAME_INDEXED = 1; - static final int APP_CERTIFICATE_INDEXED = 2; - - static final String DEFAULT_RULE_KEY = "N/A"; - - /** Represents which indexed file the rule should be located. */ - @IntDef( - value = { - NOT_INDEXED, - PACKAGE_NAME_INDEXED, - APP_CERTIFICATE_INDEXED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface IndexType { - } - - private @IndexType int mIndexType; - private String mRuleKey; - - /** Constructor without a ruleKey for {@code NOT_INDEXED}. */ - RuleIndexingDetails(@IndexType int indexType) { - this.mIndexType = indexType; - this.mRuleKey = DEFAULT_RULE_KEY; - } - - /** Constructor with a ruleKey for indexed rules. */ - RuleIndexingDetails(@IndexType int indexType, String ruleKey) { - this.mIndexType = indexType; - this.mRuleKey = ruleKey; - } - - /** Returns the indexing type for the rule. */ - @IndexType - public int getIndexType() { - return mIndexType; - } - - /** Returns the identified rule key. */ - public String getRuleKey() { - return mRuleKey; - } -} diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java deleted file mode 100644 index e7235591fb9b..000000000000 --- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED; - -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.Rule; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** A helper class for identifying the indexing type and key of a given rule. */ -class RuleIndexingDetailsIdentifier { - - /** - * Splits a given rule list into three indexing categories. Each rule category is returned as a - * TreeMap that is sorted by their indexing keys -- where keys correspond to package name for - * PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for - * NOT_INDEXED rules. - */ - public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets( - List<Rule> rules) { - if (rules == null) { - throw new IllegalArgumentException( - "Index buckets cannot be created for null rule list."); - } - - Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap(); - typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap()); - typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>()); - typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>()); - - // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the - // entries sorted by their index key. - for (Rule rule : rules) { - RuleIndexingDetails indexingDetails; - try { - indexingDetails = getIndexingDetails(rule.getFormula()); - } catch (Exception e) { - throw new IllegalArgumentException( - String.format("Malformed rule identified. [%s]", rule.toString())); - } - - int ruleIndexType = indexingDetails.getIndexType(); - String ruleKey = indexingDetails.getRuleKey(); - - if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) { - typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList()); - } - - typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule); - } - - return typeOrganizedRuleMap; - } - - private static RuleIndexingDetails getIndexingDetails(IntegrityFormula formula) { - switch (formula.getTag()) { - case IntegrityFormula.COMPOUND_FORMULA_TAG: - return getIndexingDetailsForCompoundFormula((CompoundFormula) formula); - case IntegrityFormula.STRING_ATOMIC_FORMULA_TAG: - return getIndexingDetailsForStringAtomicFormula( - (AtomicFormula.StringAtomicFormula) formula); - case IntegrityFormula.LONG_ATOMIC_FORMULA_TAG: - case IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG: - case IntegrityFormula.BOOLEAN_ATOMIC_FORMULA_TAG: - // Package name and app certificate related formulas are string atomic formulas. - return new RuleIndexingDetails(NOT_INDEXED); - default: - throw new IllegalArgumentException( - String.format("Invalid formula tag type: %s", formula.getTag())); - } - } - - private static RuleIndexingDetails getIndexingDetailsForCompoundFormula( - CompoundFormula compoundFormula) { - int connector = compoundFormula.getConnector(); - List<IntegrityFormula> formulas = compoundFormula.getFormulas(); - - switch (connector) { - case CompoundFormula.AND: - case CompoundFormula.OR: - // If there is a package name related atomic rule, return package name indexed. - Optional<RuleIndexingDetails> packageNameRule = - formulas.stream() - .map(formula -> getIndexingDetails(formula)) - .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType() - == PACKAGE_NAME_INDEXED) - .findAny(); - if (packageNameRule.isPresent()) { - return packageNameRule.get(); - } - - // If there is an app certificate related atomic rule but no package name related - // atomic rule, return app certificate indexed. - Optional<RuleIndexingDetails> appCertificateRule = - formulas.stream() - .map(formula -> getIndexingDetails(formula)) - .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType() - == APP_CERTIFICATE_INDEXED) - .findAny(); - if (appCertificateRule.isPresent()) { - return appCertificateRule.get(); - } - - // Do not index when there is not package name or app certificate indexing. - return new RuleIndexingDetails(NOT_INDEXED); - default: - // Having a NOT operator in the indexing messes up the indexing; e.g., deny - // installation if app certificate is NOT X (should not be indexed with app cert - // X). We will not keep these rules indexed. - // Also any other type of unknown operators will not be indexed. - return new RuleIndexingDetails(NOT_INDEXED); - } - } - - private static RuleIndexingDetails getIndexingDetailsForStringAtomicFormula( - AtomicFormula.StringAtomicFormula atomicFormula) { - switch (atomicFormula.getKey()) { - case AtomicFormula.PACKAGE_NAME: - return new RuleIndexingDetails(PACKAGE_NAME_INDEXED, atomicFormula.getValue()); - case AtomicFormula.APP_CERTIFICATE: - return new RuleIndexingDetails(APP_CERTIFICATE_INDEXED, atomicFormula.getValue()); - default: - return new RuleIndexingDetails(NOT_INDEXED); - } - } -} - diff --git a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java deleted file mode 100644 index 022b4b8cb7eb..000000000000 --- a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import static com.android.server.integrity.parser.RuleMetadataParser.RULE_PROVIDER_TAG; -import static com.android.server.integrity.parser.RuleMetadataParser.VERSION_TAG; - -import android.util.Xml; - -import com.android.modules.utils.TypedXmlSerializer; -import com.android.server.integrity.model.RuleMetadata; - -import org.xmlpull.v1.XmlSerializer; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; - -/** Helper class for writing rule metadata. */ -public class RuleMetadataSerializer { - /** Serialize the rule metadata to an output stream. */ - public static void serialize(RuleMetadata ruleMetadata, OutputStream outputStream) - throws IOException { - TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(outputStream); - - serializeTaggedValue(xmlSerializer, RULE_PROVIDER_TAG, ruleMetadata.getRuleProvider()); - serializeTaggedValue(xmlSerializer, VERSION_TAG, ruleMetadata.getVersion()); - - xmlSerializer.endDocument(); - } - - private static void serializeTaggedValue(TypedXmlSerializer xmlSerializer, String tag, - String value) throws IOException { - xmlSerializer.startTag(/* namespace= */ null, tag); - xmlSerializer.text(value); - xmlSerializer.endTag(/* namespace= */ null, tag); - } -} diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java deleted file mode 100644 index 2941856915a8..000000000000 --- a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import android.content.integrity.Rule; - -import java.io.OutputStream; -import java.util.List; -import java.util.Optional; - -/** A helper class to serialize rules from the {@link Rule} model. */ -public interface RuleSerializer { - - /** Serialize rules to an output stream */ - void serialize( - List<Rule> rules, - Optional<Integer> formatVersion, - OutputStream ruleFileOutputStream, - OutputStream indexingFileOutputStream) - throws RuleSerializeException; - - /** Serialize rules to a ByteArray. */ - byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion) - throws RuleSerializeException; -} 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/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..dd6c59fbea18 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; @@ -7770,10 +7770,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 +8013,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()) 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/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java index 20cca969dade..af2bb17fd0e6 100644 --- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java +++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java @@ -404,9 +404,11 @@ public class BackgroundInstallControlService extends SystemService { void handlePackageRemove(String packageName, int userId) { initBackgroundInstalledPackages(); + if (mBackgroundInstalledPackages.contains(userId, packageName)) { + mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL); + } mBackgroundInstalledPackages.remove(userId, packageName); writeBackgroundInstalledPackagesToDisk(); - mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL); } void handleUsageEvent(UsageEvents.Event event, int userId) { diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 355184e1c758..d9e76966892c 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -85,6 +85,7 @@ import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX; import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN; import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE; import static com.android.server.pm.PackageManagerService.TAG; +import static com.android.server.pm.PackageManagerService.WATCHDOG_TIMEOUT; import static com.android.server.pm.PackageManagerServiceUtils.comparePackageSignatures; import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures; import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists; @@ -133,9 +134,11 @@ import android.os.Build; import android.os.Environment; import android.os.FileUtils; import android.os.Message; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.SELinux; +import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; @@ -174,7 +177,6 @@ import com.android.internal.util.CollectionUtils; import com.android.server.EventLogTags; import com.android.server.SystemConfig; import com.android.server.criticalevents.CriticalEventLog; -import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.DexManager; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; @@ -210,6 +212,10 @@ import java.util.concurrent.ExecutorService; final class InstallPackageHelper { + // One minute over PM WATCHDOG_TIMEOUT + private static final long WAKELOCK_TIMEOUT_MS = WATCHDOG_TIMEOUT + 1000 * 60; + private static final String INSTALLER_WAKE_LOCK_TAG = "installer:packages"; + private final PackageManagerService mPm; private final AppDataHelper mAppDataHelper; private final BroadcastHelper mBroadcastHelper; @@ -218,14 +224,16 @@ final class InstallPackageHelper { private final IncrementalManager mIncrementalManager; private final ApexManager mApexManager; private final DexManager mDexManager; - private final ArtManagerService mArtManagerService; private final Context mContext; - private final PackageDexOptimizer mPackageDexOptimizer; private final PackageAbiHelper mPackageAbiHelper; private final SharedLibrariesImpl mSharedLibraries; private final PackageManagerServiceInjector mInjector; private final UpdateOwnershipHelper mUpdateOwnershipHelper; + private final Object mInternalLock = new Object(); + @GuardedBy("mInternalLock") + private PowerManager.WakeLock mInstallingWakeLock; + // TODO(b/198166813): remove PMS dependency InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper, @@ -241,9 +249,7 @@ final class InstallPackageHelper { mIncrementalManager = pm.mInjector.getIncrementalManager(); mApexManager = pm.mInjector.getApexManager(); mDexManager = pm.mInjector.getDexManager(); - mArtManagerService = pm.mInjector.getArtManagerService(); mContext = pm.mInjector.getContext(); - mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer(); mPackageAbiHelper = pm.mInjector.getAbiHelper(); mSharedLibraries = pm.mInjector.getSharedLibrariesImpl(); mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper(); @@ -1013,6 +1019,7 @@ final class InstallPackageHelper { boolean success = false; final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size()); final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size()); + final long acquireTime = acquireWakeLock(requests.size()); try { CriticalEventLog.getInstance().logInstallPackagesStarted(); if (prepareInstallPackages(requests) @@ -1033,6 +1040,46 @@ final class InstallPackageHelper { } finally { completeInstallProcess(requests, createdAppId, success); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + releaseWakeLock(acquireTime, requests.size()); + } + } + + private long acquireWakeLock(int count) { + if (!mPm.isSystemReady()) { + return -1; + } + synchronized (mInternalLock) { + if (mInstallingWakeLock == null) { + PowerManager pwm = mContext.getSystemService(PowerManager.class); + if (pwm != null) { + mInstallingWakeLock = pwm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + INSTALLER_WAKE_LOCK_TAG); + } else { + Slog.w(TAG, "Unable to obtain power manager while obtaining wake lock"); + return -1; + } + } + + mInstallingWakeLock.acquire(WAKELOCK_TIMEOUT_MS * count); + return SystemClock.elapsedRealtime(); + } + } + + private void releaseWakeLock(final long acquireTime, int count) { + if (acquireTime < 0) { + return; + } + synchronized (mInternalLock) { + try { + if (mInstallingWakeLock == null) { + return; + } + if (mInstallingWakeLock.isHeld()) { + mInstallingWakeLock.release(); + } + } catch (RuntimeException e) { + Slog.wtf(TAG, "Error while releasing installer lock", e); + } } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 7ecfe7f64ffe..498659427a21 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -341,6 +341,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 +1395,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; - } - // 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; - } - } + 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 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() { 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 5fc3e332b95c..24933cab2a32 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1015,7 +1015,8 @@ 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) { + if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED + && result != PermissionChecker.PERMISSION_SOFT_DENIED) { if (attributedOp == AppOpsManager.OP_NONE) { finishDataDelivery(AppOpsManager.permissionToOpCode(permission), attributionSource.asState(), fromDatasource); @@ -1244,6 +1245,7 @@ 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, @@ -1310,8 +1312,21 @@ public class PermissionManagerService extends IPermissionManager.Stub { selfAccess, singleReceiverFromDatasource, attributedOp, proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); - switch (opMode) { - case AppOpsManager.MODE_ERRORED: { + 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) { 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: " @@ -1319,8 +1334,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { + current); } return PermissionChecker.PERMISSION_HARD_DENIED; - } - case AppOpsManager.MODE_IGNORED: { + } else { return PermissionChecker.PERMISSION_SOFT_DENIED; } } @@ -1335,6 +1349,8 @@ 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/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java index a1236e533beb..4f67318faddb 100644 --- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java +++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java @@ -32,6 +32,7 @@ import android.content.pm.PackageManager; import android.content.res.XmlResourceParser; import android.graphics.drawable.Icon; import android.hardware.input.AppLaunchData; +import android.hardware.input.InputGestureData; import android.hardware.input.KeyGestureEvent; import android.os.Handler; import android.os.RemoteException; @@ -769,6 +770,30 @@ public class ModifierShortcutManager { shortcuts); } + /** + * @return a {@link KeyboardShortcutGroup} containing the application launch keyboard + * shortcuts based on provided list of shortcut data. + */ + public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId, + List<InputGestureData> shortcutData) { + List<KeyboardShortcutInfo> shortcuts = new ArrayList<>(); + KeyCharacterMap kcm = KeyCharacterMap.load(deviceId); + for (InputGestureData data : shortcutData) { + if (data.getTrigger() instanceof InputGestureData.KeyTrigger trigger) { + KeyboardShortcutInfo info = shortcutInfoFromIntent( + kcm.getDisplayLabel(trigger.getKeycode()), + getIntentFromAppLaunchData(data.getAction().appLaunchData()), + (trigger.getModifierState() & KeyEvent.META_SHIFT_ON) != 0); + if (info != null) { + shortcuts.add(info); + } + } + } + return new KeyboardShortcutGroup( + mContext.getString(R.string.keyboard_shortcut_group_applications), + shortcuts); + } + private Intent getIntentFromAppLaunchData(@NonNull AppLaunchData data) { Context context = mContext.createContextAsUser(mCurrentUser, 0); synchronized (mAppIntentCache) { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 2893430572d8..fc24e62de04e 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3414,6 +3414,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) { + if (useKeyGestureEventHandler()) { + return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId, + mInputManager.getAppLaunchBookmarks()); + } return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId); } @@ -4004,14 +4008,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private boolean interceptSystemKeysAndShortcutsNew(IBinder focusedToken, KeyEvent event) { final int keyCode = event.getKeyCode(); final int metaState = event.getMetaState(); - final boolean keyguardOn = keyguardOn(); - if (isUserSetupComplete() && !keyguardOn) { - if (mModifierShortcutManager.interceptKey(event)) { - dismissKeyboardShortcutsMenu(); - return true; - } - } switch (keyCode) { case KeyEvent.KEYCODE_HOME: return handleHomeShortcuts(focusedToken, event); @@ -4753,6 +4750,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService) throws RemoteException { synchronized (mLock) { + if (useKeyGestureEventHandler()) { + mInputManagerInternal.registerShortcutKey(shortcutCode, shortcutService); + return; + } mModifierShortcutManager.registerShortcutKey(shortcutCode, shortcutService); } } diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java index a928814c7909..01a2045df426 100644 --- a/services/core/java/com/android/server/power/PowerGroup.java +++ b/services/core/java/com/android/server/power/PowerGroup.java @@ -42,6 +42,8 @@ import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.LatencyTracker; +import com.android.server.LocalServices; +import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.power.feature.PowerManagerFlags; /** @@ -56,6 +58,11 @@ public class PowerGroup { private static final String TAG = PowerGroup.class.getSimpleName(); private static final boolean DEBUG = false; + /** + * Indicates that the default dim/sleep timeouts should be used. + */ + private static final long INVALID_TIMEOUT = -1; + @VisibleForTesting final DisplayPowerRequest mDisplayPowerRequest = new DisplayPowerRequest(); private final PowerGroupListener mWakefulnessListener; @@ -91,6 +98,9 @@ public class PowerGroup { private @PowerManager.GoToSleepReason int mLastSleepReason = PowerManager.GO_TO_SLEEP_REASON_UNKNOWN; + private final long mDimDuration; + private final long mScreenOffTimeout; + PowerGroup(int groupId, PowerGroupListener wakefulnessListener, Notifier notifier, DisplayManagerInternal displayManagerInternal, int wakefulness, boolean ready, boolean supportsSandman, long eventTime, PowerManagerFlags featureFlags) { @@ -104,6 +114,30 @@ public class PowerGroup { mLastWakeTime = eventTime; mLastSleepTime = eventTime; mFeatureFlags = featureFlags; + + long dimDuration = INVALID_TIMEOUT; + long screenOffTimeout = INVALID_TIMEOUT; + if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower() + && mGroupId != Display.DEFAULT_DISPLAY_GROUP) { + VirtualDeviceManagerInternal vdm = + LocalServices.getService(VirtualDeviceManagerInternal.class); + if (vdm != null) { + int[] displayIds = mDisplayManagerInternal.getDisplayIdsForGroup(mGroupId); + if (displayIds != null && displayIds.length > 0) { + int deviceId = vdm.getDeviceIdForDisplayId(displayIds[0]); + if (vdm.isValidVirtualDeviceId(deviceId)) { + dimDuration = vdm.getDimDurationMillisForDeviceId(deviceId); + screenOffTimeout = vdm.getScreenOffTimeoutMillisForDeviceId(deviceId); + if (dimDuration > 0 && dimDuration > screenOffTimeout) { + // If the dim duration is set, cap it to the screen off timeout. + dimDuration = screenOffTimeout; + } + } + } + } + } + mDimDuration = dimDuration; + mScreenOffTimeout = screenOffTimeout; } PowerGroup(int wakefulness, PowerGroupListener wakefulnessListener, Notifier notifier, @@ -119,6 +153,16 @@ public class PowerGroup { mLastWakeTime = eventTime; mLastSleepTime = eventTime; mFeatureFlags = featureFlags; + mDimDuration = INVALID_TIMEOUT; + mScreenOffTimeout = INVALID_TIMEOUT; + } + + long getScreenOffTimeoutOverrideLocked(long defaultScreenOffTimeout) { + return mScreenOffTimeout == INVALID_TIMEOUT ? defaultScreenOffTimeout : mScreenOffTimeout; + } + + long getScreenDimDurationOverrideLocked(long defaultScreenDimDuration) { + return mDimDuration == INVALID_TIMEOUT ? defaultScreenDimDuration : mDimDuration; } long getLastWakeTimeLocked() { diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 3a5afacd0977..0acfe92f578d 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -2971,8 +2971,8 @@ public final class PowerManagerService extends SystemService mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT); final long attentiveTimeout = getAttentiveTimeoutLocked(); - final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout); - final long defaultScreenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout, + final long defaultSleepTimeout = getSleepTimeoutLocked(attentiveTimeout); + final long defaultScreenOffTimeout = getScreenOffTimeoutLocked(defaultSleepTimeout, attentiveTimeout); final long defaultScreenDimDuration = getScreenDimDurationLocked(defaultScreenOffTimeout); @@ -2985,13 +2985,25 @@ public final class PowerManagerService extends SystemService final PowerGroup powerGroup = mPowerGroups.valueAt(idx); final int wakefulness = powerGroup.getWakefulnessLocked(); - // The default display screen timeout could be overridden by policy. + // The timeouts could be overridden by the power group policy. long screenOffTimeout = defaultScreenOffTimeout; long screenDimDuration = defaultScreenDimDuration; + long sleepTimeout = defaultSleepTimeout; + // TODO(b/376211497): Consolidate the timeout logic for all power groups. if (powerGroup.getGroupId() == Display.DEFAULT_DISPLAY_GROUP) { screenOffTimeout = - getScreenOffTimeoutOverrideLocked(screenOffTimeout, screenDimDuration); + getDefaultGroupScreenOffTimeoutOverrideLocked(screenOffTimeout, + screenDimDuration); screenDimDuration = getScreenDimDurationLocked(screenOffTimeout); + } else { + screenOffTimeout = powerGroup.getScreenOffTimeoutOverrideLocked(screenOffTimeout); + screenDimDuration = + powerGroup.getScreenDimDurationOverrideLocked(screenDimDuration); + if (sleepTimeout > 0 && screenOffTimeout > 0) { + // If both sleep and screen off timeouts are set, make sure that the sleep + // timeout is not smaller than the screen off one. + sleepTimeout = Math.max(sleepTimeout, screenOffTimeout); + } } if (wakefulness != WAKEFULNESS_ASLEEP) { @@ -3273,7 +3285,8 @@ public final class PowerManagerService extends SystemService @VisibleForTesting @GuardedBy("mLock") - long getScreenOffTimeoutOverrideLocked(long screenOffTimeout, long screenDimDuration) { + long getDefaultGroupScreenOffTimeoutOverrideLocked(long screenOffTimeout, + long screenDimDuration) { long shortestScreenOffTimeout = screenOffTimeout; if (mScreenTimeoutOverridePolicy != null) { shortestScreenOffTimeout = diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 48174a6bad11..940a5091a4be 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -809,17 +809,16 @@ public class BatteryStatsImpl extends BatteryStats { mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, elapsedRealtimeMs); if (u.mChildUids != null) { - LongArrayMultiStateCounter.LongArrayContainer deltaContainer = - getCpuTimeInFreqContainer(); + long[] delta = getCpuTimeInFreqContainer(); int childUidCount = u.mChildUids.size(); for (int j = childUidCount - 1; j >= 0; --j) { LongArrayMultiStateCounter cpuTimeInFreqCounter = u.mChildUids.valueAt(j).cpuTimeInFreqCounter; if (cpuTimeInFreqCounter != null) { mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j), - cpuTimeInFreqCounter, elapsedRealtimeMs, deltaContainer); - onBatteryCounter.addCounts(deltaContainer); - onBatteryScreenOffCounter.addCounts(deltaContainer); + cpuTimeInFreqCounter, elapsedRealtimeMs, delta); + onBatteryCounter.addCounts(delta); + onBatteryScreenOffCounter.addCounts(delta); } } } @@ -890,8 +889,7 @@ public class BatteryStatsImpl extends BatteryStats { if (childUid != null) { final LongArrayMultiStateCounter counter = childUid.cpuTimeInFreqCounter; if (counter != null) { - final LongArrayMultiStateCounter.LongArrayContainer deltaContainer = - getCpuTimeInFreqContainer(); + final long[] deltaContainer = getCpuTimeInFreqContainer(); mKernelSingleUidTimeReader.addDelta(uid, counter, elapsedRealtimeMs, deltaContainer); onBatteryCounter.addCounts(deltaContainer); @@ -1741,7 +1739,7 @@ public class BatteryStatsImpl extends BatteryStats { private long mBatteryTimeToFullSeconds = -1; - private LongArrayMultiStateCounter.LongArrayContainer mTmpCpuTimeInFreq; + private long[] mTmpCpuTimeInFreq; /** * Times spent by the system server threads handling incoming binder requests. @@ -10956,9 +10954,7 @@ public class BatteryStatsImpl extends BatteryStats { // Set initial values to all 0. This is a child UID and we want to include // the entirety of its CPU time-in-freq stats into the parent's stats. - cpuTimeInFreqCounter.updateValues( - new LongArrayMultiStateCounter.LongArrayContainer(cpuFreqCount), - timestampMs); + cpuTimeInFreqCounter.updateValues(new long[cpuFreqCount], timestampMs); } else { cpuTimeInFreqCounter = null; } @@ -11361,11 +11357,9 @@ public class BatteryStatsImpl extends BatteryStats { } @GuardedBy("this") - private LongArrayMultiStateCounter.LongArrayContainer getCpuTimeInFreqContainer() { + private long[] getCpuTimeInFreqContainer() { if (mTmpCpuTimeInFreq == null) { - mTmpCpuTimeInFreq = - new LongArrayMultiStateCounter.LongArrayContainer( - mCpuScalingPolicies.getScalingStepCount()); + mTmpCpuTimeInFreq = new long[mCpuScalingPolicies.getScalingStepCount()]; } return mTmpCpuTimeInFreq; } diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java index e798bc487031..3f7fcee5bb9a 100644 --- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java +++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java @@ -19,6 +19,7 @@ package com.android.server.stats.pull.netstats; import android.annotation.NonNull; import android.net.NetworkStats; import android.net.NetworkTemplate; +import android.util.Log; import java.util.Objects; @@ -33,6 +34,7 @@ import java.util.Objects; */ public class NetworkStatsAccumulator { + private static final String TAG = "NetworkStatsAccumulator"; private final NetworkTemplate mTemplate; private final boolean mWithTags; private final long mBucketDurationMillis; @@ -57,8 +59,9 @@ public class NetworkStatsAccumulator { @NonNull public NetworkStats queryStats(long currentTimeMillis, @NonNull StatsQueryFunction queryFunction) { - maybeExpandSnapshot(currentTimeMillis, queryFunction); - return snapshotPlusFollowingStats(currentTimeMillis, queryFunction); + NetworkStats completeStats = snapshotPlusFollowingStats(currentTimeMillis, queryFunction); + maybeExpandSnapshot(currentTimeMillis, completeStats, queryFunction); + return completeStats; } /** @@ -72,15 +75,28 @@ public class NetworkStatsAccumulator { * Expands the internal cumulative stats snapshot, if possible, by querying NetworkStats. */ private void maybeExpandSnapshot(long currentTimeMillis, + NetworkStats completeStatsUntilCurrentTime, @NonNull StatsQueryFunction queryFunction) { // Update snapshot only if it is possible to expand it by at least one full bucket, and only // if the new snapshot's end is not in the active bucket. long newEndTimeMillis = currentTimeMillis - mBucketDurationMillis; if (newEndTimeMillis - mSnapshotEndTimeMillis > mBucketDurationMillis) { - NetworkStats extraStats = queryFunction.queryNetworkStats(mTemplate, mWithTags, - mSnapshotEndTimeMillis, newEndTimeMillis); + Log.v(TAG, + "Expanding snapshot (mTemplate=" + mTemplate + ", mWithTags=" + mWithTags + + ") from " + mSnapshotEndTimeMillis + " to " + newEndTimeMillis + + " at " + currentTimeMillis); + NetworkStats extraStats = queryFunction.queryNetworkStats( + mTemplate, mWithTags, mSnapshotEndTimeMillis, newEndTimeMillis); mSnapshot = mSnapshot.add(extraStats); mSnapshotEndTimeMillis = newEndTimeMillis; + + // NetworkStats queries interpolate historical data using integers maths, which makes + // queries non-transitive: Query(t0, t1) + Query(t1, t2) <= Query(t0, t2). + // Compute interpolation data loss from moving the snapshot's end-point, and add it to + // the snapshot to avoid under-counting. + NetworkStats newStats = snapshotPlusFollowingStats(currentTimeMillis, queryFunction); + NetworkStats interpolationLoss = completeStatsUntilCurrentTime.subtract(newStats); + mSnapshot = mSnapshot.add(interpolationLoss); } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 69643002750b..73ae51c6e64a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -707,9 +707,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ private boolean mOccludesParent; - /** Whether the activity have style floating */ - private boolean mStyleFloating; - /** * Unlike {@link #mOccludesParent} which can be changed at runtime. This is a static attribute * from the style of activity. Because we don't want {@link WindowContainer#getOrientation()} @@ -791,10 +788,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false. private boolean mIsEligibleForFixedOrientationLetterbox; - // activity is not displayed? - // TODO: rename to mNoDisplay - @VisibleForTesting - boolean noDisplay; + /** + * Whether the activity is to be displayed. See {@link android.R.attr#windowNoDisplay}. + */ + private boolean mNoDisplay; final boolean mShowForAllUsers; // TODO: Make this final int mTargetSdk; @@ -1178,7 +1175,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.print(" inHistory="); pw.print(inHistory); pw.print(" idle="); pw.println(idle); pw.print(prefix); pw.print("occludesParent="); pw.print(occludesParent()); - pw.print(" noDisplay="); pw.print(noDisplay); + pw.print(" mNoDisplay="); pw.print(mNoDisplay); pw.print(" immersive="); pw.print(immersive); pw.print(" launchMode="); pw.println(launchMode); pw.print(prefix); pw.print("mActivityType="); @@ -2011,20 +2008,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (ent != null) { final boolean styleTranslucent = ent.array.getBoolean( com.android.internal.R.styleable.Window_windowIsTranslucent, false); - mStyleFloating = ent.array.getBoolean( + final boolean styleFloating = ent.array.getBoolean( com.android.internal.R.styleable.Window_windowIsFloating, false); - mOccludesParent = !(styleTranslucent || mStyleFloating) + mOccludesParent = !(styleTranslucent || styleFloating) // This style is propagated to the main window attributes with // FLAG_SHOW_WALLPAPER from PhoneWindow#generateLayout. || ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false); mStyleFillsParent = mOccludesParent; - noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false); + mNoDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false); mOptOutEdgeToEdge = ent.array.getBoolean( R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false); } else { - mStyleFloating = false; mStyleFillsParent = mOccludesParent = true; - noDisplay = false; + mNoDisplay = false; mOptOutEdgeToEdge = false; } @@ -3091,8 +3087,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return occludesParent(true /* includingFinishing */); } - boolean isStyleFloating() { - return mStyleFloating; + boolean isNoDisplay() { + return mNoDisplay; + } + + /** + * Exposed only for testing and should not be used to modify value of {@link #mNoDisplay}. + */ + @VisibleForTesting + void setIsNoDisplay(boolean isNoDisplay) { + mNoDisplay = isNoDisplay; } /** Returns true if this activity is not finishing, is opaque and fills the entire space of @@ -3192,6 +3196,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mWmService.mConstants.isPackageOptOutIgnoreActivityOrientationRequest(packageName)) { return false; } + if (mAppCompatController.mAllowRestrictedResizability.getAsBoolean()) { + return false; + } // If the user preference respects aspect ratio, then it becomes non-resizable. return !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides() .shouldApplyUserMinAspectRatioOverride(); @@ -6069,7 +6076,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void notifyUnknownVisibilityLaunchedForKeyguardTransition() { // No display activities never add a window, so there is no point in waiting them for // relayout. - if (noDisplay || !isKeyguardLocked()) { + if (mNoDisplay || !isKeyguardLocked()) { return; } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 05a96d9fcf5e..2e2ca147dcdd 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1037,7 +1037,6 @@ class ActivityStarter { mLastStartReason = request.reason; mLastStartActivityTimeMs = System.currentTimeMillis(); - final ActivityRecord previousStart = mLastStartActivityRecord; final IApplicationThread caller = request.caller; Intent intent = request.intent; NeededUriGrants intentGrants = request.intentGrants; @@ -2350,7 +2349,8 @@ class ActivityStarter { // When there is a reused activity and the current result is a trampoline activity, // set the reused activity as the result. if (mLastStartActivityRecord != null - && (mLastStartActivityRecord.finishing || mLastStartActivityRecord.noDisplay)) { + && (mLastStartActivityRecord.finishing + || mLastStartActivityRecord.isNoDisplay())) { mLastStartActivityRecord = targetTaskTop; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 30b53d1dbab4..cf178046d2e6 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2478,7 +2478,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { /** Notifies that the top activity of the task is forced to be resizeable. */ private void handleForcedResizableTaskIfNeeded(Task task, int reason) { final ActivityRecord topActivity = task.getTopNonFinishingActivity(); - if (topActivity == null || topActivity.noDisplay + if (topActivity == null || topActivity.isNoDisplay() || !topActivity.canForceResizeNonResizable(task.getWindowingMode())) { return; } @@ -2894,10 +2894,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { private boolean mIncludeInvisibleAndFinishing; private boolean mIgnoringKeyguard; - ActivityRecord getOpaqueActivity( - @NonNull WindowContainer<?> container, boolean ignoringKeyguard) { + ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) { mIncludeInvisibleAndFinishing = true; - mIgnoringKeyguard = ignoringKeyguard; + mIgnoringKeyguard = true; return container.getActivity(this, true /* traverseTopToBottom */, null /* boundary */); } diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java index 548c0a34bf99..fa2c71658022 100644 --- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java @@ -128,10 +128,11 @@ class AppCompatAspectRatioPolicy { } if (!aspectRatioOverrides.shouldOverrideMinAspectRatio() && !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) { - if (mActivityRecord.isUniversalResizeable()) { + final float minAspectRatio = info.getMinAspectRatio(); + if (minAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) { return 0; } - return info.getMinAspectRatio(); + return minAspectRatio; } if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY) @@ -173,10 +174,11 @@ class AppCompatAspectRatioPolicy { if (mTransparentPolicy.isRunning()) { return mTransparentPolicy.getInheritedMaxAspectRatio(); } - if (mActivityRecord.isUniversalResizeable()) { + final float maxAspectRatio = mActivityRecord.info.getMaxAspectRatio(); + if (maxAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) { return 0; } - return mActivityRecord.info.getMaxAspectRatio(); + return maxAspectRatio; } @Nullable diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index 6c344c6d850a..145a3767c149 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -15,12 +15,15 @@ */ package com.android.server.wm; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY; + import android.annotation.NonNull; import android.content.pm.PackageManager; import com.android.server.wm.utils.OptPropFactory; import java.io.PrintWriter; +import java.util.function.BooleanSupplier; /** * Allows the interaction with all the app compat policies and configurations @@ -47,6 +50,8 @@ class AppCompatController { private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy; @NonNull private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy; + @NonNull + final BooleanSupplier mAllowRestrictedResizability; AppCompatController(@NonNull WindowManagerService wmService, @NonNull ActivityRecord activityRecord) { @@ -70,6 +75,17 @@ class AppCompatController { mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration); mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord, mAppCompatOverrides); + mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> { + try { + return packageManager.getPropertyAsUser( + PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, + mActivityRecord.mActivityComponent.getPackageName(), + mActivityRecord.mActivityComponent.getClassName(), + mActivityRecord.mUserId).getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + }); } @NonNull 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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e827f44cb2a2..e9e550e72a00 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -159,7 +159,6 @@ import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELD import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields; import static com.android.server.wm.utils.RegionUtils.forEachRectReverse; import static com.android.server.wm.utils.RegionUtils.rectListToRegion; -import static com.android.window.flags.Flags.explicitRefreshRateHints; import android.annotation.IntDef; import android.annotation.NonNull; @@ -3426,14 +3425,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (!mWmService.mSupportsHighPerfTransitions) { return; } - if (!explicitRefreshRateHints()) { - if (enable) { - getPendingTransaction().setEarlyWakeupStart(); - } else { - getPendingTransaction().setEarlyWakeupEnd(); - } - return; - } if (enable) { if (mTransitionPrefSession == null) { mTransitionPrefSession = mWmService.mSystemPerformanceHinter.createSession( @@ -3446,10 +3437,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void enableHighFrameRate(boolean enable) { - if (!explicitRefreshRateHints()) { - // Done by RefreshRatePolicy. - return; - } if (enable) { if (mHighFrameRateSession == null) { mHighFrameRateSession = mWmService.mSystemPerformanceHinter.createSession( @@ -7072,7 +7059,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Override public void setImeInputTargetRequestedVisibility(boolean visible) { if (android.view.inputmethod.Flags.refactorInsetsController()) { - // TODO(b/329229469) we won't have the statsToken in all cases, but should still log + // TODO(b/353463205) we won't have the statsToken in all cases, but should still log try { mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 8627a473a1ff..76e8a70768c1 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1735,9 +1735,9 @@ public class DisplayPolicy { } // Show IME over the keyguard if the target allows it. - final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisible() - && win.mIsImWindow && (imeTarget.canShowWhenLocked() - || !imeTarget.canBeHiddenByKeyguard()); + final boolean showImeOverKeyguard = + imeTarget != null && win.mIsImWindow && imeTarget.isDisplayed() && ( + imeTarget.canShowWhenLocked() || !imeTarget.canBeHiddenByKeyguard()); if (showImeOverKeyguard) { return false; } diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index e9c6e93891df..5ed9612e4e83 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -100,13 +100,13 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { // isLeashReadyForDispatching (used to dispatch the leash of the control) is // depending on mGivenInsetsReady. Therefore, triggering notifyControlChanged here // again, so that the control with leash can be eventually dispatched - if (!mGivenInsetsReady && mServerVisible && !givenInsetsPending) { + if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending) { mGivenInsetsReady = true; ImeTracker.forLogging().onProgress(mStatsToken, ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED); mStateController.notifyControlChanged(mControlTarget, this); setImeShowing(true); - } else if (wasServerVisible && mServerVisible && mGivenInsetsReady + } else if (wasServerVisible && isServerVisible() && mGivenInsetsReady && givenInsetsPending) { // If the server visibility didn't change (still visible), and mGivenInsetsReady // is set, we won't call into notifyControlChanged. Therefore, we can reset the @@ -114,7 +114,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { ImeTracker.forLogging().onCancelled(mStatsToken, ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED); mStatsToken = null; - } else if (wasServerVisible && !mServerVisible) { + } else if (wasServerVisible && !isServerVisible()) { setImeShowing(false); } } @@ -134,11 +134,15 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { @Override protected boolean isLeashReadyForDispatching() { if (android.view.inputmethod.Flags.refactorInsetsController()) { + // We should only dispatch the leash, if the following conditions are fulfilled: + // 1. parent isLeashReadyForDispatching, 2. mGivenInsetsReady (means there are no + // givenInsetsPending), 3. the IME surface is drawn, 4. either the IME is + // serverVisible (the unfrozen state) final WindowState ws = mWindowContainer != null ? mWindowContainer.asWindowState() : null; final boolean isDrawn = ws != null && ws.isDrawn(); return super.isLeashReadyForDispatching() - && mServerVisible && isDrawn && mGivenInsetsReady; + && isServerVisible() && isDrawn && mGivenInsetsReady; } else { return super.isLeashReadyForDispatching(); } @@ -254,7 +258,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { // Refer WindowState#getImeControlTarget(). target = target.getWindow().getImeControlTarget(); } - // TODO(b/329229469) make sure that the statsToken of all callers is non-null (currently + // TODO(b/353463205) make sure that the statsToken of all callers is non-null (currently // not the case) super.updateControlForTarget(target, force, statsToken); if (Flags.refactorInsetsController()) { @@ -290,12 +294,14 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate(); if (Flags.refactorInsetsController()) { if (changed) { + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY); invokeOnImeRequestedChangedListener(mDisplayContent.getImeInputTarget(), statsToken); } else { - // TODO(b/329229469) change phase and check cancelled / failed + // TODO(b/353463205) check cancelled / failed ImeTracker.forLogging().onCancelled(statsToken, - ImeTracker.PHASE_CLIENT_REPORT_REQUESTED_VISIBLE_TYPES); + ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY); } } return changed; @@ -460,7 +466,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { // This can later become ready, so we don't want to cancel the pending request here. return; } - // TODO(b/329229469) check if this is still triggered, as we don't go into STATE_SHOW_IME + // TODO(b/353463205) check if this is still triggered, as we don't go into STATE_SHOW_IME // (DefaultImeVisibilityApplier) if (android.view.inputmethod.Flags.refactorInsetsController()) { // The IME is drawn, so call into {@link WindowState#notifyInsetsControlChanged} diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 1d4d6eb82c44..7276007481ab 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -730,6 +730,10 @@ class InsetsSourceProvider { return mFakeControlTarget; } + boolean isServerVisible() { + return mServerVisible; + } + boolean isClientVisible() { return mClientVisible; } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 5dddf36a8d8b..4b2d45430bb4 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -317,9 +317,9 @@ class InsetsStateController { // aborted. provider.updateFakeControlTarget(target); } else { - // TODO(b/329229469) if the IME controlTarget changes, any pending requests should fail + // TODO(b/353463205) if the IME controlTarget changes, any pending requests should fail provider.updateControlForTarget(target, false /* force */, - null /* TODO(b/329229469) check if needed here */); + null /* TODO(b/353463205) check if needed here */); // Get control target again in case the provider didn't accept the one we passed to it. target = provider.getControlTarget(); diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java index 8cab7d979d07..e4c34ed52359 100644 --- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java +++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java @@ -19,8 +19,6 @@ package com.android.server.wm; import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE; import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY; -import static com.android.window.flags.Flags.explicitRefreshRateHints; - import android.hardware.display.DisplayManager; import android.view.Display; import android.view.Display.Mode; @@ -60,7 +58,6 @@ class RefreshRatePolicy { } private final DisplayInfo mDisplayInfo; - private final Mode mDefaultMode; private final Mode mLowRefreshRateMode; private final PackageRefreshRate mNonHighRefreshRatePackages = new PackageRefreshRate(); private final HighRefreshRateDenylist mHighRefreshRateDenylist; @@ -92,8 +89,7 @@ class RefreshRatePolicy { RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo, HighRefreshRateDenylist denylist) { mDisplayInfo = displayInfo; - mDefaultMode = displayInfo.getDefaultMode(); - mLowRefreshRateMode = findLowRefreshRateMode(displayInfo, mDefaultMode); + mLowRefreshRateMode = findLowRefreshRateMode(displayInfo); mHighRefreshRateDenylist = denylist; mWmService = wmService; } @@ -102,7 +98,8 @@ class RefreshRatePolicy { * Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the * default mode. */ - private Mode findLowRefreshRateMode(DisplayInfo displayInfo, Mode defaultMode) { + private Mode findLowRefreshRateMode(DisplayInfo displayInfo) { + final Mode defaultMode = displayInfo.getDefaultMode(); float[] refreshRates = displayInfo.getDefaultRefreshRates(); float bestRefreshRate = defaultMode.getRefreshRate(); mMinSupportedRefreshRate = bestRefreshRate; @@ -135,33 +132,6 @@ class RefreshRatePolicy { // Unspecified, use default mode. return 0; } - - // If app is animating, it's not able to control refresh rate because we want the animation - // to run in default refresh rate. But if the display size of default mode is different - // from the using preferred mode, then still keep the preferred mode to avoid disturbing - // the animation. - if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) { - Display.Mode preferredMode = null; - for (Display.Mode mode : mDisplayInfo.supportedModes) { - if (preferredDisplayModeId == mode.getModeId()) { - preferredMode = mode; - break; - } - } - if (preferredMode != null) { - final int pW = preferredMode.getPhysicalWidth(); - final int pH = preferredMode.getPhysicalHeight(); - if ((pW != mDefaultMode.getPhysicalWidth() - || pH != mDefaultMode.getPhysicalHeight()) - && pW == mDisplayInfo.getNaturalWidth() - && pH == mDisplayInfo.getNaturalHeight()) { - // Prefer not to change display size when animating. - return preferredDisplayModeId; - } - } - return 0; - } - return preferredDisplayModeId; } @@ -264,12 +234,6 @@ class RefreshRatePolicy { return w.mFrameRateVote.reset(); } - // If app is animating, it's not able to control refresh rate because we want the animation - // to run in default refresh rate. - if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) { - return w.mFrameRateVote.reset(); - } - // If the app set a preferredDisplayModeId, the preferred refresh rate is the refresh rate // of that mode id. if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) { diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 077127c031e1..1bb4c41e79e0 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -715,7 +715,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { if (embeddedWindow != null) { // If there is no WindowState for the IWindow, it could be still an // EmbeddedWindow. Therefore, check the EmbeddedWindowController as well - // TODO(b/329229469) Use different phase here + // TODO(b/353463205) Use different phase here ImeTracker.forLogging().onProgress(imeStatsToken, ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES); embeddedWindow.setRequestedVisibleTypes( diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 7473accb8eee..352dc528f815 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3432,9 +3432,9 @@ class Task extends TaskFragment { info.isFocused = isFocused(); info.isVisible = hasVisibleChildren(); info.isVisibleRequested = isVisibleRequested(); + info.isTopActivityNoDisplay = top != null && top.isNoDisplay(); info.isSleeping = shouldSleepActivities(); info.isTopActivityTransparent = top != null && !top.fillsParent(); - info.isTopActivityStyleFloating = top != null && top.isStyleFloating(); info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds; final WindowState windowState = top != null ? top.findMainWindow() : null; info.requestedVisibleTypes = (windowState != null && Flags.enableFullyImmersiveInDesktop()) @@ -4724,7 +4724,7 @@ class Task extends TaskFragment { } } if (likelyResolvedMode != WINDOWING_MODE_FULLSCREEN - && topActivity != null && !topActivity.noDisplay + && topActivity != null && !topActivity.isNoDisplay() && topActivity.canForceResizeNonResizable(likelyResolvedMode)) { // Inform the user that they are starting an app that may not work correctly in // multi-window mode. diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 606d51d8ec50..e090b1980c6d 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1090,8 +1090,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { return true; } // Including finishing Activity if the TaskFragment is becoming invisible in the transition. - return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this, - true /* ignoringKeyguard */) == null; + return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null; } /** @@ -1734,6 +1733,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (!hasDirectChildActivities()) { return false; } + if (mResumedActivity != null && mTransitionController.isTransientLaunch(mResumedActivity)) { + // Even if the transient activity is occluded, defer pausing (addToStopping will still + // be called) it until the transient transition is done. So the current resuming + // activity won't need to wait for additional pause complete. + return false; + } ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this, mResumedActivity); diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index 5c9a84db002a..c39671d76929 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -449,7 +449,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // If the source activity is a no-display activity, pass on the launch display area token // from source activity as currently preferred. - if (taskDisplayArea == null && source != null && source.noDisplay) { + if (taskDisplayArea == null && source != null && source.isNoDisplay()) { taskDisplayArea = source.mHandoverTaskDisplayArea; if (taskDisplayArea != null) { if (DEBUG) appendLog("display-area-from-no-display-source=" + taskDisplayArea); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 454e43120ede..a6034664af5a 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -477,20 +477,17 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (transientRoot == null) continue; final WindowContainer<?> rootParent = transientRoot.getParent(); if (rootParent == null || rootParent.getTopChild() == transientRoot) continue; - final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper - .getOpaqueActivity(rootParent, true /* ignoringKeyguard */); - if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) { - occludedCount++; + for (int j = rootParent.getChildCount() - 1; j >= 0; --j) { + final WindowContainer<?> sibling = rootParent.getChildAt(j); + if (sibling == transientRoot) break; + if (!sibling.getWindowConfiguration().isAlwaysOnTop() && mController.mAtm + .mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(sibling) != null) { + occludedCount++; + break; + } } } if (occludedCount == numTransient) { - for (int i = mTransientLaunches.size() - 1; i >= 0; --i) { - if (mTransientLaunches.keyAt(i).isDescendantOf(task)) { - // Keep transient activity visible until transition finished, so it won't pause - // with transient-hide tasks that may delay resuming the next top. - return true; - } - } // Let transient-hide activities pause before transition is finished. return false; } @@ -3589,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/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index d01e29b2fd5e..079170a70433 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -157,6 +157,7 @@ import static com.android.server.wm.WindowStateProto.ANIMATING_EXIT; import static com.android.server.wm.WindowStateProto.ANIMATOR; import static com.android.server.wm.WindowStateProto.ATTRIBUTES; import static com.android.server.wm.WindowStateProto.DESTROYING; +import static com.android.server.wm.WindowStateProto.DIM_BOUNDS; import static com.android.server.wm.WindowStateProto.DISPLAY_ID; import static com.android.server.wm.WindowStateProto.FORCE_SEAMLESS_ROTATION; import static com.android.server.wm.WindowStateProto.GIVEN_CONTENT_INSETS; @@ -181,7 +182,6 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY; import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES; -import static com.android.window.flags.Flags.explicitRefreshRateHints; import static com.android.window.flags.Flags.secureWindowState; import static com.android.window.flags.Flags.surfaceTrustedOverlay; @@ -4118,6 +4118,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mMergedLocalInsetsSources.valueAt(i).dumpDebug(proto, MERGED_LOCAL_INSETS_SOURCES); } } + if (getDimController() != null) { + final Rect dimBounds = getDimController().getDimBounds(); + if (dimBounds != null) { + dimBounds.dumpDebug(proto, DIM_BOUNDS); + } + } proto.end(token); } @@ -5297,12 +5303,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (voteChanged) { getPendingTransaction() .setFrameRate(mSurfaceControl, mFrameRateVote.mRefreshRate, - mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS); - if (explicitRefreshRateHints()) { - getPendingTransaction().setFrameRateSelectionStrategy(mSurfaceControl, - mFrameRateVote.mSelectionStrategy); - } - + mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS) + .setFrameRateSelectionStrategy(mSurfaceControl, + mFrameRateVote.mSelectionStrategy); } } 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/jni/Android.bp b/services/core/jni/Android.bp index ea0b02c58974..4dc3ca5ceea5 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -55,7 +55,6 @@ cc_library_static { "com_android_server_powerstats_PowerStatsService.cpp", "com_android_server_power_stats_CpuPowerStatsCollector.cpp", "com_android_server_hint_HintManagerService.cpp", - "com_android_server_SerialService.cpp", "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp", "com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp", "com_android_server_stats_pull_StatsPullAtomService.cpp", diff --git a/services/core/jni/com_android_server_SerialService.cpp b/services/core/jni/com_android_server_SerialService.cpp deleted file mode 100644 index 6600c981b68d..000000000000 --- a/services/core/jni/com_android_server_SerialService.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2011 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. - */ - -#define LOG_TAG "SerialServiceJNI" -#include "utils/Log.h" - -#include "jni.h" -#include <nativehelper/JNIPlatformHelp.h> -#include "android_runtime/AndroidRuntime.h" - -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> - -namespace android -{ - -static struct parcel_file_descriptor_offsets_t -{ - jclass mClass; - jmethodID mConstructor; -} gParcelFileDescriptorOffsets; - -static jobject android_server_SerialService_open(JNIEnv *env, jobject /* thiz */, jstring path) -{ - const char *pathStr = env->GetStringUTFChars(path, NULL); - - int fd = open(pathStr, O_RDWR | O_NOCTTY); - if (fd < 0) { - ALOGE("could not open %s", pathStr); - env->ReleaseStringUTFChars(path, pathStr); - return NULL; - } - env->ReleaseStringUTFChars(path, pathStr); - - jobject fileDescriptor = jniCreateFileDescriptor(env, fd); - if (fileDescriptor == NULL) { - close(fd); - return NULL; - } - return env->NewObject(gParcelFileDescriptorOffsets.mClass, - gParcelFileDescriptorOffsets.mConstructor, fileDescriptor); -} - - -static const JNINativeMethod method_table[] = { - { "native_open", "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;", - (void*)android_server_SerialService_open }, -}; - -int register_android_server_SerialService(JNIEnv *env) -{ - jclass clazz = env->FindClass("com/android/server/SerialService"); - if (clazz == NULL) { - ALOGE("Can't find com/android/server/SerialService"); - return -1; - } - - clazz = env->FindClass("android/os/ParcelFileDescriptor"); - LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor"); - gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V"); - LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL, - "Unable to find constructor for android.os.ParcelFileDescriptor"); - - return jniRegisterNativeMethods(env, "com/android/server/SerialService", - method_table, NELEM(method_table)); -} - -}; diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 3c55d18245d7..59d7365d957d 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -33,7 +33,6 @@ int register_android_server_PowerStatsService(JNIEnv* env); int register_android_server_power_stats_CpuPowerStatsCollector(JNIEnv* env); int register_android_server_HintManagerService(JNIEnv* env); int register_android_server_storage_AppFuse(JNIEnv* env); -int register_android_server_SerialService(JNIEnv* env); int register_android_server_SystemServer(JNIEnv* env); int register_android_server_UsbAlsaJackDetector(JNIEnv* env); int register_android_server_UsbAlsaMidiDevice(JNIEnv* env); @@ -94,7 +93,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_PowerStatsService(env); register_android_server_power_stats_CpuPowerStatsCollector(env); register_android_server_HintManagerService(env); - register_android_server_SerialService(env); register_android_server_InputManager(env); register_android_server_LightsService(env); register_android_server_UsbDeviceManager(vm, env); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index a58da81c6396..984105865f7d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -90,6 +90,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletableFuture; /** * Class responsible for setting, resolving, and enforcing policies set by multiple management @@ -1030,11 +1031,11 @@ final class DevicePolicyEngine { } } - private <V> void enforcePolicy(PolicyDefinition<V> policyDefinition, + private <V> CompletableFuture<Boolean> enforcePolicy(PolicyDefinition<V> policyDefinition, @Nullable PolicyValue<V> policyValue, int userId) { // null policyValue means remove any enforced policies, ensure callbacks handle this // properly - policyDefinition.enforcePolicy( + return policyDefinition.enforcePolicy( policyValue == null ? null : policyValue.getValue(), mContext, userId); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index aca6f7235714..4ce18d232936 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; /** @@ -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 " @@ -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; + } } } @@ -24250,6 +23810,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 +24232,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..952bbca58f3b 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; @@ -424,6 +428,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); } @@ -497,7 +505,9 @@ class OwnersData { 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 f271162bbfa4..f1711f5f8c0b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -53,6 +53,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletableFuture; final class PolicyDefinition<V> { @@ -336,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<>(); @@ -382,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); @@ -504,7 +514,8 @@ final class PolicyDefinition<V> { private final int mPolicyFlags; // A function that accepts policy to apply, context, userId, callback arguments, and returns // true if the policy has been enforced successfully. - private final QuadFunction<V, Context, Integer, PolicyKey, Boolean> mPolicyEnforcerCallback; + private final QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>> + mPolicyEnforcerCallback; private final PolicySerializer<V> mPolicySerializer; private PolicyDefinition<V> createPolicyDefinition(PolicyKey key) { @@ -574,7 +585,7 @@ final class PolicyDefinition<V> { return mResolutionMechanism.resolve(adminsPolicy); } - boolean enforcePolicy(@Nullable V value, Context context, int userId) { + CompletableFuture<Boolean> enforcePolicy(@Nullable V value, Context context, int userId) { return mPolicyEnforcerCallback.apply(value, context, userId, mPolicyKey); } @@ -592,7 +603,6 @@ final class PolicyDefinition<V> { POLICY_DEFINITIONS.put(key.getIdentifier(), definition); } - /** * Callers must ensure that {@code policyType} have implemented an appropriate * {@link Object#equals} implementation. @@ -600,7 +610,8 @@ final class PolicyDefinition<V> { private PolicyDefinition( @NonNull PolicyKey key, ResolutionMechanism<V> resolutionMechanism, - QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback, + QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>> + policyEnforcerCallback, PolicySerializer<V> policySerializer) { this(key, resolutionMechanism, POLICY_FLAG_NONE, policyEnforcerCallback, policySerializer); } @@ -610,10 +621,11 @@ final class PolicyDefinition<V> { * {@link Object#equals} and {@link Object#hashCode()} implementation. */ private PolicyDefinition( - @NonNull PolicyKey policyKey, + @NonNull PolicyKey policyKey, ResolutionMechanism<V> resolutionMechanism, int policyFlags, - QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback, + QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>> + policyEnforcerCallback, PolicySerializer<V> policySerializer) { Objects.requireNonNull(policyKey); mPolicyKey = policyKey; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 4d9abf1d6be0..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; @@ -55,6 +56,7 @@ import android.util.ArraySet; import android.util.Slog; import android.view.IWindowManager; +import com.android.internal.infra.AndroidFuture; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; @@ -65,6 +67,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -73,33 +76,36 @@ final class PolicyEnforcerCallbacks { private static final String LOG_TAG = "PolicyEnforcerCallbacks"; - static <T> boolean noOp(T value, Context context, Integer userId, PolicyKey policyKey) { - return true; + static <T> CompletableFuture<Boolean> noOp(T value, Context context, Integer userId, + PolicyKey policyKey) { + return AndroidFuture.completedFuture(true); } - static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) { + static CompletableFuture<Boolean> setAutoTimezoneEnabled(@Nullable Boolean enabled, + @NonNull Context context) { if (!Flags.setAutoTimeZoneEnabledCoexistence()) { Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off."); - return true; + return AndroidFuture.completedFuture(true); } return Binder.withCleanCallingIdentity(() -> { Objects.requireNonNull(context); int value = enabled != null && enabled ? 1 : 0; - return Settings.Global.putInt( - context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE, - value); + return AndroidFuture.completedFuture( + Settings.Global.putInt( + context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE, + value)); }); } - static boolean setPermissionGrantState( + static CompletableFuture<Boolean> setPermissionGrantState( @Nullable Integer grantState, @NonNull Context context, int userId, @NonNull PolicyKey policyKey) { if (!Flags.setPermissionGrantStateCoexistence()) { Slogf.w(LOG_TAG, "Trying to enforce setPermissionGrantState while flag is off."); - return true; + return AndroidFuture.completedFuture(true); } - return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> { + return Binder.withCleanCallingIdentity(() -> { if (!(policyKey instanceof PackagePermissionPolicyKey)) { throw new IllegalArgumentException("policyKey is not of type " + "PermissionGrantStatePolicyKey, passed in policyKey is: " + policyKey); @@ -125,12 +131,13 @@ final class PolicyEnforcerCallbacks { .setRuntimePermissionGrantStateByDeviceAdmin(context.getPackageName(), permissionParams, context.getMainExecutor(), callback::trigger); try { - return callback.await(20_000, TimeUnit.MILLISECONDS); + return AndroidFuture.completedFuture( + callback.await(20_000, TimeUnit.MILLISECONDS)); } catch (Exception e) { // TODO: add logging - return false; + return AndroidFuture.completedFuture(false); } - })); + }); } @NonNull @@ -149,23 +156,23 @@ final class PolicyEnforcerCallbacks { } } - static boolean enforceSecurityLogging( + static CompletableFuture<Boolean> enforceSecurityLogging( @Nullable Boolean value, @NonNull Context context, int userId, @NonNull PolicyKey policyKey) { final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class); dpmi.enforceSecurityLoggingPolicy(Boolean.TRUE.equals(value)); - return true; + return AndroidFuture.completedFuture(true); } - static boolean enforceAuditLogging( + static CompletableFuture<Boolean> enforceAuditLogging( @Nullable Boolean value, @NonNull Context context, int userId, @NonNull PolicyKey policyKey) { final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class); dpmi.enforceAuditLoggingPolicy(Boolean.TRUE.equals(value)); - return true; + return AndroidFuture.completedFuture(true); } - static boolean setLockTask( + static CompletableFuture<Boolean> setLockTask( @Nullable LockTaskPolicy policy, @NonNull Context context, int userId) { List<String> packages = Collections.emptyList(); int flags = LockTaskPolicy.DEFAULT_LOCK_TASK_FLAG; @@ -175,7 +182,7 @@ final class PolicyEnforcerCallbacks { } DevicePolicyManagerService.updateLockTaskPackagesLocked(context, packages, userId); DevicePolicyManagerService.updateLockTaskFeaturesLocked(flags, userId); - return true; + return AndroidFuture.completedFuture(true); } @@ -187,8 +194,8 @@ final class PolicyEnforcerCallbacks { * rely on the POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED flag so DPE only invokes this callback * when the policy is set, and not during system boot or other situations. */ - static boolean setApplicationRestrictions(Bundle bundle, Context context, Integer userId, - PolicyKey policyKey) { + static CompletableFuture<Boolean> setApplicationRestrictions(Bundle bundle, Context context, + Integer userId, PolicyKey policyKey) { Binder.withCleanCallingIdentity(() -> { PackagePolicyKey key = (PackagePolicyKey) policyKey; String packageName = key.getPackageName(); @@ -198,12 +205,13 @@ final class PolicyEnforcerCallbacks { changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); context.sendBroadcastAsUser(changeIntent, UserHandle.of(userId)); }); - return true; + return AndroidFuture.completedFuture(true); } 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(); @@ -220,7 +228,7 @@ final class PolicyEnforcerCallbacks { // TODO: when a local policy exists for a user, this callback will be invoked for this user // individually as well as for USER_ALL. This can be optimized by separating local and global // enforcement in the policy engine. - static boolean setUserControlDisabledPackages( + static CompletableFuture<Boolean> setUserControlDisabledPackages( @Nullable Set<String> packages, Context context, int userId, PolicyKey policyKey) { Binder.withCleanCallingIdentity(() -> { PackageManagerInternal pmi = @@ -246,7 +254,7 @@ final class PolicyEnforcerCallbacks { } } }); - return true; + return AndroidFuture.completedFuture(true); } /** Handles USER_ALL expanding it into the list of all intact users. */ @@ -271,7 +279,7 @@ final class PolicyEnforcerCallbacks { } } - static boolean addPersistentPreferredActivity( + static CompletableFuture<Boolean> addPersistentPreferredActivity( @Nullable ComponentName preferredActivity, @NonNull Context context, int userId, @NonNull PolicyKey policyKey) { Binder.withCleanCallingIdentity(() -> { @@ -297,13 +305,13 @@ final class PolicyEnforcerCallbacks { Slog.wtf(LOG_TAG, "Error adding/removing persistent preferred activity", re); } }); - return true; + return AndroidFuture.completedFuture(true); } - static boolean setUninstallBlocked( + static CompletableFuture<Boolean> setUninstallBlocked( @Nullable Boolean uninstallBlocked, @NonNull Context context, int userId, @NonNull PolicyKey policyKey) { - return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> { + return Binder.withCleanCallingIdentity(() -> { if (!(policyKey instanceof PackagePolicyKey)) { throw new IllegalArgumentException("policyKey is not of type " + "PackagePolicyKey, passed in policyKey is: " + policyKey); @@ -314,14 +322,14 @@ final class PolicyEnforcerCallbacks { packageName, uninstallBlocked != null && uninstallBlocked, userId); - return true; - })); + return AndroidFuture.completedFuture(true); + }); } - static boolean setUserRestriction( + static CompletableFuture<Boolean> setUserRestriction( @Nullable Boolean enabled, @NonNull Context context, int userId, @NonNull PolicyKey policyKey) { - return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> { + return Binder.withCleanCallingIdentity(() -> { if (!(policyKey instanceof UserRestrictionPolicyKey)) { throw new IllegalArgumentException("policyKey is not of type " + "UserRestrictionPolicyKey, passed in policyKey is: " + policyKey); @@ -331,14 +339,14 @@ final class PolicyEnforcerCallbacks { UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class); userManager.setUserRestriction( userId, parsedKey.getRestriction(), enabled != null && enabled); - return true; - })); + return AndroidFuture.completedFuture(true); + }); } - static boolean setApplicationHidden( + static CompletableFuture<Boolean> setApplicationHidden( @Nullable Boolean hide, @NonNull Context context, int userId, @NonNull PolicyKey policyKey) { - return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> { + return Binder.withCleanCallingIdentity(() -> { if (!(policyKey instanceof PackagePolicyKey)) { throw new IllegalArgumentException("policyKey is not of type " + "PackagePolicyKey, passed in policyKey is: " + policyKey); @@ -346,12 +354,13 @@ final class PolicyEnforcerCallbacks { PackagePolicyKey parsedKey = (PackagePolicyKey) policyKey; String packageName = Objects.requireNonNull(parsedKey.getPackageName()); IPackageManager packageManager = AppGlobals.getPackageManager(); - return packageManager.setApplicationHiddenSettingAsUser( - packageName, hide != null && hide, userId); - })); + return AndroidFuture.completedFuture( + packageManager.setApplicationHiddenSettingAsUser( + packageName, hide != null && hide, userId)); + }); } - static boolean setScreenCaptureDisabled( + static CompletableFuture<Boolean> setScreenCaptureDisabled( @Nullable Boolean disabled, @NonNull Context context, int userId, @NonNull PolicyKey policyKey) { Binder.withCleanCallingIdentity(() -> { @@ -363,10 +372,10 @@ final class PolicyEnforcerCallbacks { updateScreenCaptureDisabled(); } }); - return true; + return AndroidFuture.completedFuture(true); } - static boolean setContentProtectionPolicy( + static CompletableFuture<Boolean> setContentProtectionPolicy( @Nullable Integer value, @NonNull Context context, @UserIdInt Integer userId, @@ -378,7 +387,7 @@ final class PolicyEnforcerCallbacks { cacheImpl.setContentProtectionPolicy(userId, value); } }); - return true; + return AndroidFuture.completedFuture(true); } private static void updateScreenCaptureDisabled() { @@ -393,7 +402,7 @@ final class PolicyEnforcerCallbacks { }); } - static boolean setPersonalAppsSuspended( + static CompletableFuture<Boolean> setPersonalAppsSuspended( @Nullable Boolean suspended, @NonNull Context context, int userId, @NonNull PolicyKey policyKey) { Binder.withCleanCallingIdentity(() -> { @@ -404,7 +413,7 @@ final class PolicyEnforcerCallbacks { .unsuspendAdminSuspendedPackages(userId); } }); - return true; + return AndroidFuture.completedFuture(true); } private static void suspendPersonalAppsInPackageManager(Context context, int userId) { @@ -418,13 +427,53 @@ final class PolicyEnforcerCallbacks { } } - static boolean setUsbDataSignalingEnabled(@Nullable Boolean value, @NonNull Context context) { + static CompletableFuture<Boolean> setUsbDataSignalingEnabled(@Nullable Boolean value, + @NonNull Context context) { return Binder.withCleanCallingIdentity(() -> { Objects.requireNonNull(context); boolean enabled = value == null || value; DevicePolicyManagerService.updateUsbDataSignal(context, enabled); - return true; + 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/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS index dc5cb8d6bdf5..0c9f70ca96b5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/OWNERS +++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS @@ -1,6 +1,6 @@ per-file *Alarm* = file:/apex/jobscheduler/OWNERS per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS -per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS +per-file *DeviceIdleController* = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS per-file SensitiveContentProtectionManagerService* = file:/core/java/android/permission/OWNERS per-file RescuePartyTest.java = file:/packages/CrashRecovery/OWNERS per-file *Storage* = file:/core/java/android/os/storage/OWNERS diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index b0053581963a..4a1315583ad4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -60,6 +60,7 @@ import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_AP import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ; import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ; +import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ; import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND; import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT; import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW; @@ -129,6 +130,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; /** * Test class for {@link OomAdjuster}. @@ -899,8 +901,25 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test + public void testUpdateOomAdj_DoPending_PreviousApp() { + testUpdateOomAdj_PreviousApp(apps -> { + for (ProcessRecord app : apps) { + mProcessStateController.enqueueUpdateTarget(app); + } + mProcessStateController.runPendingUpdate(OOM_ADJ_REASON_NONE); + }); + } + + @SuppressWarnings("GuardedBy") + @Test public void testUpdateOomAdj_DoAll_PreviousApp() { - final int numberOfApps = 15; + testUpdateOomAdj_PreviousApp(apps -> { + mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE); + }); + } + + private void testUpdateOomAdj_PreviousApp(Consumer<ProcessRecord[]> updater) { + final int numberOfApps = 105; final ProcessRecord[] apps = new ProcessRecord[numberOfApps]; for (int i = 0; i < numberOfApps; i++) { apps[i] = spy(makeDefaultProcessRecord(MOCKAPP_PID + i, MOCKAPP_UID + i, @@ -911,10 +930,11 @@ public class MockingOomAdjusterTests { } setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE); setProcessesToLru(apps); - mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE); - + updater.accept(apps); for (int i = 0; i < numberOfApps; i++) { - assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ, + final int mruIndex = numberOfApps - i - 1; + final int expectedAdj = Math.min(PREVIOUS_APP_ADJ + mruIndex, PREVIOUS_APP_MAX_ADJ); + assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, expectedAdj, SCHED_GROUP_BACKGROUND, "previous"); } @@ -3184,7 +3204,8 @@ public class MockingOomAdjusterTests { setProcessesToLru(app1, app2); mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE); - assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ, + assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY, + PREVIOUS_APP_ADJ + (Flags.oomadjusterPrevLaddering() ? 1 : 0), SCHED_GROUP_BACKGROUND, "recent-provider"); assertProcStates(app2, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ, SCHED_GROUP_BACKGROUND, "recent-provider"); 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/powerservicetests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java index 648da6530eb0..4e29e74651b6 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java @@ -49,17 +49,23 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.Context; import android.hardware.display.DisplayManagerInternal; import android.os.PowerManager; import android.os.PowerSaveState; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.view.Display; import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.util.LatencyTracker; +import com.android.server.LocalServices; +import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.power.feature.PowerManagerFlags; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -73,6 +79,9 @@ import org.mockito.MockitoAnnotations; public class PowerGroupTest { private static final int GROUP_ID = 0; + private static final int NON_DEFAULT_GROUP_ID = 1; + private static final int NON_DEFAULT_DISPLAY_ID = 2; + private static final int VIRTUAL_DEVICE_ID = 3; private static final int UID = 11; private static final long TIMESTAMP_CREATE = 1; private static final long TIMESTAMP1 = 999; @@ -87,10 +96,16 @@ public class PowerGroupTest { private static final LatencyTracker LATENCY_TRACKER = LatencyTracker.getInstance( InstrumentationRegistry.getInstrumentation().getContext()); + private static final long DEFAULT_TIMEOUT = 1234L; + + @Rule + public SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private PowerGroup mPowerGroup; @Mock private PowerGroup.PowerGroupListener mWakefulnessCallbackMock; @Mock private Notifier mNotifier; @Mock private DisplayManagerInternal mDisplayManagerInternal; + @Mock private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal; @Mock private PowerManagerFlags mFeatureFlags; @@ -740,4 +755,111 @@ public class PowerGroupTest { assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of( brightnessFactor); } + + @Test + public void testTimeoutsOverride_defaultGroup_noOverride() { + assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT)) + .isEqualTo(DEFAULT_TIMEOUT); + assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT)) + .isEqualTo(DEFAULT_TIMEOUT); + } + + @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @Test + public void testTimeoutsOverride_noVdm_noOverride() { + LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); + LocalServices.addService(VirtualDeviceManagerInternal.class, null); + + mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier, + mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true, + /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags); + + assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT)) + .isEqualTo(DEFAULT_TIMEOUT); + assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT)) + .isEqualTo(DEFAULT_TIMEOUT); + } + + @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @Test + public void testTimeoutsOverride_notValidVirtualDeviceId_noOverride() { + LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); + LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal); + + when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID)) + .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID}); + when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID)) + .thenReturn(Context.DEVICE_ID_DEFAULT); + when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(Context.DEVICE_ID_DEFAULT)) + .thenReturn(false); + + mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier, + mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true, + /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags); + + assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT)) + .isEqualTo(DEFAULT_TIMEOUT); + assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT)) + .isEqualTo(DEFAULT_TIMEOUT); + } + + @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @Test + public void testTimeoutsOverride_validVirtualDeviceId_timeoutsAreOverridden() { + LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); + LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal); + + final long dimDurationOverride = DEFAULT_TIMEOUT * 3; + final long screenOffTimeoutOverride = DEFAULT_TIMEOUT * 5; + + when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID)) + .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID}); + when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID)) + .thenReturn(VIRTUAL_DEVICE_ID); + when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(VIRTUAL_DEVICE_ID)) + .thenReturn(true); + when(mVirtualDeviceManagerInternal.getDimDurationMillisForDeviceId(VIRTUAL_DEVICE_ID)) + .thenReturn(dimDurationOverride); + when(mVirtualDeviceManagerInternal.getScreenOffTimeoutMillisForDeviceId(VIRTUAL_DEVICE_ID)) + .thenReturn(screenOffTimeoutOverride); + + mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier, + mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true, + /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags); + + assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT)) + .isEqualTo(dimDurationOverride); + assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT)) + .isEqualTo(screenOffTimeoutOverride); + } + + @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @Test + public void testTimeoutsOverrides_dimDurationIsCapped() { + LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); + LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal); + + final long dimDurationOverride = DEFAULT_TIMEOUT * 5; + final long screenOffTimeoutOverride = DEFAULT_TIMEOUT * 3; + + when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID)) + .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID}); + when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID)) + .thenReturn(VIRTUAL_DEVICE_ID); + when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(VIRTUAL_DEVICE_ID)) + .thenReturn(true); + when(mVirtualDeviceManagerInternal.getDimDurationMillisForDeviceId(VIRTUAL_DEVICE_ID)) + .thenReturn(dimDurationOverride); + when(mVirtualDeviceManagerInternal.getScreenOffTimeoutMillisForDeviceId(VIRTUAL_DEVICE_ID)) + .thenReturn(screenOffTimeoutOverride); + + mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier, + mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true, + /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags); + + assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT)) + .isEqualTo(screenOffTimeoutOverride); + assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT)) + .isEqualTo(screenOffTimeoutOverride); + } } diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java index b10200daeeb4..359cf6376239 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java @@ -85,6 +85,7 @@ import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.UserHandle; import android.os.test.TestLooper; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.provider.Settings; @@ -102,6 +103,7 @@ import com.android.internal.foldables.FoldGracePeriodProvider; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.lights.LightsManager; import com.android.server.policy.WindowManagerPolicy; @@ -167,6 +169,7 @@ public class PowerManagerServiceTest { @Mock private BatteryManagerInternal mBatteryManagerInternalMock; @Mock private ActivityManagerInternal mActivityManagerInternalMock; @Mock private AttentionManagerInternal mAttentionManagerInternalMock; + @Mock private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock; @Mock private DreamManagerInternal mDreamManagerInternalMock; @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock; @Mock private FoldGracePeriodProvider mFoldGracePeriodProvider; @@ -246,6 +249,7 @@ public class PowerManagerServiceTest { addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock); addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock); addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock); + addLocalServiceMock(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternalMock); mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mResourcesSpy = spy(mContextSpy.getResources()); @@ -1200,6 +1204,59 @@ public class PowerManagerServiceTest { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); } + @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @SuppressWarnings("GuardedBy") + @Test + public void testNonDefaultDisplayGroupWithCustomTimeout_afterTimeout_goesToDozing() { + final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; + final int nonDefaultDisplayId = Display.DEFAULT_DISPLAY + 2; + final int virtualDeviceId = Context.DEVICE_ID_DEFAULT + 3; + final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener = + new AtomicReference<>(); + doAnswer((Answer<Void>) invocation -> { + listener.set(invocation.getArgument(0)); + return null; + }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any()); + final DisplayInfo info = new DisplayInfo(); + info.displayGroupId = nonDefaultDisplayGroupId; + when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplayId)).thenReturn(info); + when(mDisplayManagerInternalMock.getDisplayIdsForGroup(nonDefaultDisplayGroupId)) + .thenReturn(new int[] {nonDefaultDisplayId}); + when(mVirtualDeviceManagerInternalMock.getDeviceIdForDisplayId(nonDefaultDisplayId)) + .thenReturn(virtualDeviceId); + when(mVirtualDeviceManagerInternalMock.isValidVirtualDeviceId(virtualDeviceId)) + .thenReturn(true); + when(mVirtualDeviceManagerInternalMock + .getScreenOffTimeoutMillisForDeviceId(virtualDeviceId)) + .thenReturn(20000L); + + setMinimumScreenOffTimeoutConfig(10000); + createService(); + startSystem(); + listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId); + + assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)) + .isEqualTo(WAKEFULNESS_AWAKE); + assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)) + .isEqualTo(WAKEFULNESS_AWAKE); + + // The default timeout is 10s, the custom group timeout is 20s. + + advanceTime(15000); + + assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)) + .isEqualTo(WAKEFULNESS_ASLEEP); + assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)) + .isEqualTo(WAKEFULNESS_AWAKE); + + advanceTime(10000); + + assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)) + .isEqualTo(WAKEFULNESS_ASLEEP); + assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)) + .isEqualTo(WAKEFULNESS_DOZING); + } + @SuppressWarnings("GuardedBy") @Test public void testAmbientSuppression_disablesDreamingAndWakesDevice() { @@ -3348,7 +3405,8 @@ public class PowerManagerServiceTest { null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null /* callback */); } - assertThat(mService.getScreenOffTimeoutOverrideLocked(screenTimeout, screenDimTimeout)) + assertThat(mService.getDefaultGroupScreenOffTimeoutOverrideLocked(screenTimeout, + screenDimTimeout)) .isEqualTo(expect[2]); if (acquireWakeLock) { mService.getBinderServiceInstance().releaseWakeLock(token, 0); 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 0a1fc3184fca..f02a389a160e 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 @@ -438,10 +438,7 @@ public class BatteryStatsImplTest { doAnswer(invocation -> { LongArrayMultiStateCounter counter = invocation.getArgument(1); long timestampMs = invocation.getArgument(2); - LongArrayMultiStateCounter.LongArrayContainer container = - new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS); - container.setValues(cpuTimes); - counter.updateValues(container, timestampMs); + counter.updateValues(cpuTimes, timestampMs); return null; }).when(mKernelSingleUidTimeReader).addDelta(eq(testUid), any(LongArrayMultiStateCounter.class), anyLong()); @@ -451,20 +448,13 @@ public class BatteryStatsImplTest { doAnswer(invocation -> { LongArrayMultiStateCounter counter = invocation.getArgument(1); long timestampMs = invocation.getArgument(2); - LongArrayMultiStateCounter.LongArrayContainer deltaContainer = - invocation.getArgument(3); - - LongArrayMultiStateCounter.LongArrayContainer container = - new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS); - container.setValues(cpuTimes); - counter.updateValues(container, timestampMs); - if (deltaContainer != null) { - deltaContainer.setValues(delta); - } + long[] deltaOut = invocation.getArgument(3); + counter.updateValues(cpuTimes, timestampMs); + System.arraycopy(delta, 0, deltaOut, 0, delta.length); return null; }).when(mKernelSingleUidTimeReader).addDelta(eq(testUid), any(LongArrayMultiStateCounter.class), anyLong(), - any(LongArrayMultiStateCounter.LongArrayContainer.class)); + any(long[].class)); } @Test diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java index 71a65c85d9e1..4cea72835fba 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java @@ -394,10 +394,7 @@ public class CpuPowerCalculatorTest { doAnswer(invocation -> { LongArrayMultiStateCounter counter = invocation.getArgument(1); long timestampMs = invocation.getArgument(2); - LongArrayMultiStateCounter.LongArrayContainer container = - new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS); - container.setValues(cpuTimes); - counter.updateValues(container, timestampMs); + counter.updateValues(cpuTimes, timestampMs); return null; }).when(mMockKernelSingleUidTimeReader).addDelta(eq(uid), any(LongArrayMultiStateCounter.class), anyLong()); 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/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index e3868089b21f..727d1b59646a 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -64,6 +64,7 @@ import android.companion.virtual.audio.IAudioRoutingCallback; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorCallback; import android.companion.virtual.sensor.VirtualSensorConfig; +import android.companion.virtualdevice.flags.Flags; import android.content.AttributionSource; import android.content.ComponentName; import android.content.Context; @@ -103,6 +104,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.WorkSource; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; @@ -999,6 +1001,7 @@ public class VirtualDeviceManagerServiceTest { nullable(String.class), anyInt(), eq(null)); } + @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) @Test public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException { verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(), @@ -1010,6 +1013,7 @@ public class VirtualDeviceManagerServiceTest { nullable(String.class), eq(DISPLAY_ID_1), eq(null)); } + @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) @Test public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired() throws RemoteException { @@ -1022,6 +1026,7 @@ public class VirtualDeviceManagerServiceTest { nullable(String.class), eq(DISPLAY_ID_1), eq(null)); } + @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) @Test public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException { addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); @@ -1037,6 +1042,7 @@ public class VirtualDeviceManagerServiceTest { verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt()); } + @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) @Test public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException { addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index c741c6c041a2..077bb03c8359 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -2562,7 +2562,13 @@ public class HdmiCecLocalDevicePlaybackTest { mTestLooper.dispatchAll(); // User interacted with the DUT, so the device will not go to standby. - skipActiveSourceLostUi(0, true, true); + mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + } + }); + mTestLooper.dispatchAll(); + assertThat(mIsOnActiveSourceLostPopupActive).isFalse(); assertThat(mPowerManager.isInteractive()).isTrue(); assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue(); diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java deleted file mode 100644 index 9ed2e88bd6a2..000000000000 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java +++ /dev/null @@ -1,914 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION; -import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; -import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; -import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; -import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; -import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY; -import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE; -import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY; -import static com.android.server.integrity.serializer.RuleBinarySerializer.INDEXED_RULE_SIZE_LIMIT; -import static com.android.server.integrity.serializer.RuleBinarySerializer.NONINDEXED_RULE_SIZE_LIMIT; -import static com.android.server.integrity.utils.TestUtils.getBits; -import static com.android.server.integrity.utils.TestUtils.getBytes; -import static com.android.server.integrity.utils.TestUtils.getValueBits; -import static com.android.server.testutils.TestUtils.assertExpectException; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.integrity.AppInstallMetadata; -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.IntegrityUtils; -import android.content.integrity.Rule; - -import androidx.annotation.NonNull; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -@RunWith(JUnit4.class) -public class RuleBinarySerializerTest { - - private static final String SAMPLE_INSTALLER_NAME = "com.test.installer"; - private static final String SAMPLE_INSTALLER_CERT = "installer_cert"; - - private static final String COMPOUND_FORMULA_START_BITS = - getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS); - private static final String COMPOUND_FORMULA_END_BITS = - getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS); - private static final String ATOMIC_FORMULA_START_BITS = - getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS); - - private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS); - private static final String AND = getBits(CompoundFormula.AND, CONNECTOR_BITS); - private static final String OR = getBits(CompoundFormula.OR, CONNECTOR_BITS); - - private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS); - private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS); - private static final String INSTALLER_NAME = getBits(AtomicFormula.INSTALLER_NAME, KEY_BITS); - private static final String INSTALLER_CERTIFICATE = - getBits(AtomicFormula.INSTALLER_CERTIFICATE, KEY_BITS); - private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS); - private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS); - - private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS); - - private static final String IS_NOT_HASHED = "0"; - private static final String IS_HASHED = "1"; - - private static final String DENY = getBits(Rule.DENY, EFFECT_BITS); - - private static final String START_BIT = "1"; - private static final String END_BIT = "1"; - - private static final byte[] DEFAULT_FORMAT_VERSION_BYTES = - getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS)); - - private static final String SERIALIZED_START_INDEXING_KEY = - IS_NOT_HASHED - + getBits(START_INDEXING_KEY.length(), VALUE_SIZE_BITS) - + getValueBits(START_INDEXING_KEY); - private static final String SERIALIZED_END_INDEXING_KEY = - IS_NOT_HASHED - + getBits(END_INDEXING_KEY.length(), VALUE_SIZE_BITS) - + getValueBits(END_INDEXING_KEY); - - @Test - public void testBinaryString_serializeNullRules() { - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - assertExpectException( - RuleSerializeException.class, - /* expectedExceptionMessageRegex= */ "Null rules cannot be serialized.", - () -> binarySerializer.serialize(null, /* formatVersion= */ Optional.empty())); - } - - @Test - public void testBinaryString_emptyRules() throws Exception { - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - binarySerializer.serialize( - Collections.emptyList(), - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream); - - ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream(); - expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - assertThat(ruleOutputStream.toByteArray()) - .isEqualTo(expectedRuleOutputStream.toByteArray()); - - ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream(); - String serializedIndexingBytes = - SERIALIZED_START_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) - + SERIALIZED_END_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32); - byte[] expectedIndexingBytes = - getBytes( - serializedIndexingBytes - + serializedIndexingBytes - + serializedIndexingBytes); - expectedIndexingOutputStream.write(expectedIndexingBytes); - assertThat(indexingOutputStream.toByteArray()) - .isEqualTo(expectedIndexingOutputStream.toByteArray()); - } - - @Test - public void testBinaryStream_serializeValidCompoundFormula() throws Exception { - String packageName = "com.test.app"; - Rule rule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Collections.singletonList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false))), - Rule.DENY); - - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - binarySerializer.serialize( - Collections.singletonList(rule), - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream); - - String expectedBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream(); - expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - expectedRuleOutputStream.write(getBytes(expectedBits)); - assertThat(ruleOutputStream.toByteArray()) - .isEqualTo(expectedRuleOutputStream.toByteArray()); - - ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream(); - String expectedIndexingBitsForIndexed = - SERIALIZED_START_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) - + SERIALIZED_END_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32); - String expectedIndexingBitsForUnindexed = - SERIALIZED_START_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) - + SERIALIZED_END_INDEXING_KEY - + getBits( - DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length, - /* numOfBits= */ 32); - expectedIndexingOutputStream.write( - getBytes( - expectedIndexingBitsForIndexed - + expectedIndexingBitsForIndexed - + expectedIndexingBitsForUnindexed)); - - assertThat(indexingOutputStream.toByteArray()) - .isEqualTo(expectedIndexingOutputStream.toByteArray()); - } - - @Test - public void testBinaryString_serializeValidCompoundFormula_notConnector() throws Exception { - String packageName = "com.test.app"; - Rule rule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Collections.singletonList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false))), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeValidCompoundFormula_andConnector() throws Exception { - String packageName = "com.test.app"; - String appCertificate = "test_cert"; - Rule rule = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - appCertificate, - /* isHashedValue= */ false))), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + AND - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + ATOMIC_FORMULA_START_BITS - + APP_CERTIFICATE - + EQ - + IS_NOT_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeValidCompoundFormula_orConnector() throws Exception { - String packageName = "com.test.app"; - String appCertificate = "test_cert"; - Rule rule = - new Rule( - new CompoundFormula( - CompoundFormula.OR, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - appCertificate, - /* isHashedValue= */ false))), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + OR - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + ATOMIC_FORMULA_START_BITS - + APP_CERTIFICATE - + EQ - + IS_NOT_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeValidAtomicFormula_stringValue() throws Exception { - String packageName = "com.test.app"; - Rule rule = - new Rule( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeValidAtomicFormula_hashedValue() throws Exception { - String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; - Rule rule = - new Rule( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - IntegrityUtils.getHexDigest( - appCertificate.getBytes(StandardCharsets.UTF_8)), - /* isHashedValue= */ true), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + APP_CERTIFICATE - + EQ - + IS_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeValidAtomicFormula_integerValue() throws Exception { - long versionCode = 1; - Rule rule = - new Rule( - new AtomicFormula.LongAtomicFormula( - AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + VERSION_CODE - + EQ - + getBits(versionCode, /* numOfBits= */ 64) - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeValidAtomicFormula_booleanValue() throws Exception { - String preInstalled = "1"; - Rule rule = - new Rule( - new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true), - Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + PRE_INSTALLED - + EQ - + preInstalled - + DENY - + END_BIT; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - byteArrayOutputStream.write(getBytes(expectedBits)); - byte[] expectedRules = byteArrayOutputStream.toByteArray(); - - byte[] actualRules = - binarySerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_serializeInvalidFormulaType() throws Exception { - IntegrityFormula invalidFormula = getInvalidFormula(); - Rule rule = new Rule(invalidFormula, Rule.DENY); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - assertExpectException( - RuleSerializeException.class, - /* expectedExceptionMessageRegex= */ "Malformed rule identified.", - () -> - binarySerializer.serialize( - Collections.singletonList(rule), - /* formatVersion= */ Optional.empty())); - } - - @Test - public void testBinaryString_serializeFormatVersion() throws Exception { - int formatVersion = 1; - RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = getBits(formatVersion, FORMAT_VERSION_BITS); - byte[] expectedRules = getBytes(expectedBits); - - byte[] actualRules = - binarySerializer.serialize( - Collections.emptyList(), /* formatVersion= */ Optional.of(formatVersion)); - - assertThat(actualRules).isEqualTo(expectedRules); - } - - @Test - public void testBinaryString_verifyManyRulesAreIndexedCorrectly() throws Exception { - int ruleCount = 225; - String packagePrefix = "package.name."; - String appCertificatePrefix = "app.cert."; - String installerNamePrefix = "installer."; - - // Create the rule set with 225 package name based rules, 225 app certificate indexed rules, - // and 225 non-indexed rules.. - List<Rule> ruleList = new ArrayList(); - for (int count = 0; count < ruleCount; count++) { - ruleList.add( - getRuleWithPackageNameAndSampleInstallerName( - String.format("%s%04d", packagePrefix, count))); - } - for (int count = 0; count < ruleCount; count++) { - ruleList.add( - getRuleWithAppCertificateAndSampleInstallerName( - String.format("%s%04d", appCertificatePrefix, count))); - } - for (int count = 0; count < ruleCount; count++) { - ruleList.add( - getNonIndexedRuleWithInstallerName( - String.format("%s%04d", installerNamePrefix, count))); - } - - // Serialize the rules. - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - binarySerializer.serialize( - ruleList, - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream); - - // Verify the rules file and index files. - ByteArrayOutputStream expectedOrderedRuleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream(); - - expectedOrderedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); - int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length; - - String expectedIndexingBytesForPackageNameIndexed = - SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - for (int count = 0; count < ruleCount; count++) { - String packageName = String.format("%s%04d", packagePrefix, count); - if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) { - expectedIndexingBytesForPackageNameIndexed += - IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + getBits(totalBytesWritten, /* numOfBits= */ 32); - } - - byte[] bytesForPackage = - getBytes( - getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( - packageName)); - expectedOrderedRuleOutputStream.write(bytesForPackage); - totalBytesWritten += bytesForPackage.length; - } - expectedIndexingBytesForPackageNameIndexed += - SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - - String expectedIndexingBytesForAppCertificateIndexed = - SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - for (int count = 0; count < ruleCount; count++) { - String appCertificate = String.format("%s%04d", appCertificatePrefix, count); - if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) { - expectedIndexingBytesForAppCertificateIndexed += - IS_NOT_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + getBits(totalBytesWritten, /* numOfBits= */ 32); - } - - byte[] bytesForPackage = - getBytes( - getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( - appCertificate)); - expectedOrderedRuleOutputStream.write(bytesForPackage); - totalBytesWritten += bytesForPackage.length; - } - expectedIndexingBytesForAppCertificateIndexed += - SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - - String expectedIndexingBytesForUnindexed = - SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - for (int count = 0; count < ruleCount; count++) { - byte[] bytesForPackage = - getBytes( - getSerializedCompoundRuleWithInstallerNameAndInstallerCert( - String.format("%s%04d", installerNamePrefix, count))); - expectedOrderedRuleOutputStream.write(bytesForPackage); - totalBytesWritten += bytesForPackage.length; - } - expectedIndexingBytesForUnindexed += - SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - expectedIndexingOutputStream.write( - getBytes( - expectedIndexingBytesForPackageNameIndexed - + expectedIndexingBytesForAppCertificateIndexed - + expectedIndexingBytesForUnindexed)); - - assertThat(ruleOutputStream.toByteArray()) - .isEqualTo(expectedOrderedRuleOutputStream.toByteArray()); - assertThat(indexingOutputStream.toByteArray()) - .isEqualTo(expectedIndexingOutputStream.toByteArray()); - } - - @Test - public void testBinaryString_totalRuleSizeLimitReached() { - int ruleCount = INDEXED_RULE_SIZE_LIMIT - 1; - String packagePrefix = "package.name."; - String appCertificatePrefix = "app.cert."; - String installerNamePrefix = "installer."; - - // Create the rule set with more rules than the system can handle in total. - List<Rule> ruleList = new ArrayList(); - for (int count = 0; count < ruleCount; count++) { - ruleList.add( - getRuleWithPackageNameAndSampleInstallerName( - String.format("%s%04d", packagePrefix, count))); - } - for (int count = 0; count < ruleCount; count++) { - ruleList.add( - getRuleWithAppCertificateAndSampleInstallerName( - String.format("%s%04d", appCertificatePrefix, count))); - } - for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT - 1; count++) { - ruleList.add( - getNonIndexedRuleWithInstallerName( - String.format("%s%04d", installerNamePrefix, count))); - } - - // Serialize the rules. - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - assertExpectException( - RuleSerializeException.class, - "Too many rules provided", - () -> - binarySerializer.serialize( - ruleList, - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream)); - } - - @Test - public void testBinaryString_tooManyPackageNameIndexedRules() { - String packagePrefix = "package.name."; - - // Create a rule set with too many package name indexed rules. - List<Rule> ruleList = new ArrayList(); - for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) { - ruleList.add( - getRuleWithPackageNameAndSampleInstallerName( - String.format("%s%04d", packagePrefix, count))); - } - - // Serialize the rules. - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - assertExpectException( - RuleSerializeException.class, - "Too many rules provided in the indexing group.", - () -> - binarySerializer.serialize( - ruleList, - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream)); - } - - @Test - public void testBinaryString_tooManyAppCertificateIndexedRules() { - String appCertificatePrefix = "app.cert."; - - // Create a rule set with too many app certificate indexed rules. - List<Rule> ruleList = new ArrayList(); - for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) { - ruleList.add( - getRuleWithAppCertificateAndSampleInstallerName( - String.format("%s%04d", appCertificatePrefix, count))); - } - - // Serialize the rules. - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - assertExpectException( - RuleSerializeException.class, - "Too many rules provided in the indexing group.", - () -> - binarySerializer.serialize( - ruleList, - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream)); - } - - @Test - public void testBinaryString_tooManyNonIndexedRules() { - String installerNamePrefix = "installer."; - - // Create a rule set with too many unindexed rules. - List<Rule> ruleList = new ArrayList(); - for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT + 1; count++) { - ruleList.add( - getNonIndexedRuleWithInstallerName( - String.format("%s%04d", installerNamePrefix, count))); - } - - // Serialize the rules. - ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream(); - RuleSerializer binarySerializer = new RuleBinarySerializer(); - - assertExpectException( - RuleSerializeException.class, - "Too many rules provided in the indexing group.", - () -> - binarySerializer.serialize( - ruleList, - /* formatVersion= */ Optional.empty(), - ruleOutputStream, - indexingOutputStream)); - } - - private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) { - return new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_NAME, - SAMPLE_INSTALLER_NAME, - /* isHashedValue= */ false))), - Rule.DENY); - } - - private String getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( - String packageName) { - return START_BIT - + COMPOUND_FORMULA_START_BITS - + AND - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + ATOMIC_FORMULA_START_BITS - + INSTALLER_NAME - + EQ - + IS_NOT_HASHED - + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS) - + getValueBits(SAMPLE_INSTALLER_NAME) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - } - - private Rule getRuleWithAppCertificateAndSampleInstallerName(String certificate) { - return new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - certificate, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_NAME, - SAMPLE_INSTALLER_NAME, - /* isHashedValue= */ false))), - Rule.DENY); - } - - private String getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( - String appCertificate) { - return START_BIT - + COMPOUND_FORMULA_START_BITS - + AND - + ATOMIC_FORMULA_START_BITS - + APP_CERTIFICATE - + EQ - + IS_NOT_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + ATOMIC_FORMULA_START_BITS - + INSTALLER_NAME - + EQ - + IS_NOT_HASHED - + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS) - + getValueBits(SAMPLE_INSTALLER_NAME) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - } - - private Rule getNonIndexedRuleWithInstallerName(String installerName) { - return new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_NAME, - installerName, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_CERTIFICATE, - SAMPLE_INSTALLER_CERT, - /* isHashedValue= */ false))), - Rule.DENY); - } - - private String getSerializedCompoundRuleWithInstallerNameAndInstallerCert( - String installerName) { - return START_BIT - + COMPOUND_FORMULA_START_BITS - + AND - + ATOMIC_FORMULA_START_BITS - + INSTALLER_NAME - + EQ - + IS_NOT_HASHED - + getBits(installerName.length(), VALUE_SIZE_BITS) - + getValueBits(installerName) - + ATOMIC_FORMULA_START_BITS - + INSTALLER_CERTIFICATE - + EQ - + IS_NOT_HASHED - + getBits(SAMPLE_INSTALLER_CERT.length(), VALUE_SIZE_BITS) - + getValueBits(SAMPLE_INSTALLER_CERT) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - } - - private static IntegrityFormula getInvalidFormula() { - return new AtomicFormula(0) { - @Override - public int getTag() { - return 0; - } - - @Override - public boolean matches(AppInstallMetadata appInstallMetadata) { - return false; - } - - @Override - public boolean isAppCertificateFormula() { - return false; - } - - @Override - public boolean isAppCertificateLineageFormula() { - return false; - } - - @Override - public boolean isInstallerFormula() { - return false; - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return super.equals(obj); - } - - @NonNull - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - @Override - public String toString() { - return super.toString(); - } - - @Override - protected void finalize() throws Throwable { - super.finalize(); - } - }; - } -} diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java deleted file mode 100644 index 6dccdf51af02..000000000000 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.serializer; - -import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets; -import static com.android.server.testutils.TestUtils.assertExpectException; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.integrity.AppInstallMetadata; -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.Rule; - -import androidx.annotation.NonNull; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */ -@RunWith(JUnit4.class) -public class RuleIndexingDetailsIdentifierTest { - - private static final String SAMPLE_APP_CERTIFICATE = "testcert"; - private static final String SAMPLE_INSTALLER_NAME = "com.test.installer"; - private static final String SAMPLE_INSTALLER_CERTIFICATE = "installercert"; - private static final String SAMPLE_PACKAGE_NAME = "com.test.package"; - - private static final AtomicFormula ATOMIC_FORMULA_WITH_PACKAGE_NAME = - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - SAMPLE_PACKAGE_NAME, - /* isHashedValue= */ false); - private static final AtomicFormula ATOMIC_FORMULA_WITH_APP_CERTIFICATE = - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - SAMPLE_APP_CERTIFICATE, - /* isHashedValue= */ false); - private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_NAME = - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_NAME, - SAMPLE_INSTALLER_NAME, - /* isHashedValue= */ false); - private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE = - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_CERTIFICATE, - SAMPLE_INSTALLER_CERTIFICATE, - /* isHashedValue= */ false); - private static final AtomicFormula ATOMIC_FORMULA_WITH_VERSION_CODE = - new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE, - AtomicFormula.EQ, 12); - private static final AtomicFormula ATOMIC_FORMULA_WITH_ISPREINSTALLED = - new AtomicFormula.BooleanAtomicFormula( - AtomicFormula.PRE_INSTALLED, /* booleanValue= */ - true); - - - private static final Rule RULE_WITH_PACKAGE_NAME = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - ATOMIC_FORMULA_WITH_PACKAGE_NAME, - ATOMIC_FORMULA_WITH_INSTALLER_NAME)), - Rule.DENY); - private static final Rule RULE_WITH_APP_CERTIFICATE = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - ATOMIC_FORMULA_WITH_APP_CERTIFICATE, - ATOMIC_FORMULA_WITH_INSTALLER_NAME)), - Rule.DENY); - private static final Rule RULE_WITH_INSTALLER_RESTRICTIONS = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - ATOMIC_FORMULA_WITH_INSTALLER_NAME, - ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE)), - Rule.DENY); - - private static final Rule RULE_WITH_NONSTRING_RESTRICTIONS = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - ATOMIC_FORMULA_WITH_VERSION_CODE, - ATOMIC_FORMULA_WITH_ISPREINSTALLED)), - Rule.DENY); - public static final int INVALID_FORMULA_TAG = -1; - - @Test - public void getIndexType_nullRule() { - List<Rule> ruleList = null; - - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex= */ - "Index buckets cannot be created for null rule list.", - () -> splitRulesIntoIndexBuckets(ruleList)); - } - - @Test - public void getIndexType_invalidFormula() { - List<Rule> ruleList = new ArrayList(); - ruleList.add(new Rule(getInvalidFormula(), Rule.DENY)); - - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex= */ "Malformed rule identified.", - () -> splitRulesIntoIndexBuckets(ruleList)); - } - - @Test - public void getIndexType_ruleContainingPackageNameFormula() { - List<Rule> ruleList = new ArrayList(); - ruleList.add(RULE_WITH_PACKAGE_NAME); - - Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); - - // Verify the resulting map content. - assertThat(result.keySet()) - .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); - assertThat(result.get(NOT_INDEXED)).isEmpty(); - assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty(); - assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly(SAMPLE_PACKAGE_NAME); - assertThat(result.get(PACKAGE_NAME_INDEXED).get(SAMPLE_PACKAGE_NAME)) - .containsExactly(RULE_WITH_PACKAGE_NAME); - } - - @Test - public void getIndexType_ruleContainingAppCertificateFormula() { - List<Rule> ruleList = new ArrayList(); - ruleList.add(RULE_WITH_APP_CERTIFICATE); - - Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); - - assertThat(result.keySet()) - .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); - assertThat(result.get(NOT_INDEXED)).isEmpty(); - assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty(); - assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()) - .containsExactly(SAMPLE_APP_CERTIFICATE); - assertThat(result.get(APP_CERTIFICATE_INDEXED).get(SAMPLE_APP_CERTIFICATE)) - .containsExactly(RULE_WITH_APP_CERTIFICATE); - } - - @Test - public void getIndexType_ruleWithUnindexedCompoundFormula() { - List<Rule> ruleList = new ArrayList(); - ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS); - - Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); - - assertThat(result.keySet()) - .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); - assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty(); - assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty(); - assertThat(result.get(NOT_INDEXED).get("N/A")) - .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS); - } - - @Test - public void getIndexType_ruleContainingCompoundFormulaWithIntAndBoolean() { - List<Rule> ruleList = new ArrayList(); - ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS); - - Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); - - assertThat(result.keySet()) - .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); - assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty(); - assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty(); - assertThat(result.get(NOT_INDEXED).get("N/A")) - .containsExactly(RULE_WITH_NONSTRING_RESTRICTIONS); - } - - @Test - public void getIndexType_negatedRuleContainingPackageNameFormula() { - Rule negatedRule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Arrays.asList( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - ATOMIC_FORMULA_WITH_PACKAGE_NAME, - ATOMIC_FORMULA_WITH_APP_CERTIFICATE)))), - Rule.DENY); - List<Rule> ruleList = new ArrayList(); - ruleList.add(negatedRule); - - Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); - - assertThat(result.keySet()) - .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); - assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty(); - assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty(); - assertThat(result.get(NOT_INDEXED).get("N/A")).containsExactly(negatedRule); - } - - @Test - public void getIndexType_allRulesTogetherSplitCorrectly() { - Rule packageNameRuleA = getRuleWithPackageName("aaa"); - Rule packageNameRuleB = getRuleWithPackageName("bbb"); - Rule packageNameRuleC = getRuleWithPackageName("ccc"); - Rule certificateRule1 = getRuleWithAppCertificate("cert1"); - Rule certificateRule2 = getRuleWithAppCertificate("cert2"); - Rule certificateRule3 = getRuleWithAppCertificate("cert3"); - - List<Rule> ruleList = new ArrayList(); - ruleList.add(packageNameRuleB); - ruleList.add(packageNameRuleC); - ruleList.add(packageNameRuleA); - ruleList.add(certificateRule3); - ruleList.add(certificateRule2); - ruleList.add(certificateRule1); - ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS); - ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS); - - Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); - - assertThat(result.keySet()) - .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); - - // We check asserts this way to ensure ordering based on package name. - assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc"); - - // We check asserts this way to ensure ordering based on app certificate. - assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2", - "cert3"); - - assertThat(result.get(NOT_INDEXED).get("N/A")) - .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS, - RULE_WITH_NONSTRING_RESTRICTIONS); - } - - private Rule getRuleWithPackageName(String packageName) { - return new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - ATOMIC_FORMULA_WITH_INSTALLER_NAME)), - Rule.DENY); - } - - private Rule getRuleWithAppCertificate(String certificate) { - return new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - certificate, - /* isHashedValue= */ false), - ATOMIC_FORMULA_WITH_INSTALLER_NAME)), - Rule.DENY); - } - - private IntegrityFormula getInvalidFormula() { - return new AtomicFormula(0) { - @Override - public int getTag() { - return INVALID_FORMULA_TAG; - } - - @Override - public boolean matches(AppInstallMetadata appInstallMetadata) { - return false; - } - - @Override - public boolean isAppCertificateFormula() { - return false; - } - - @Override - public boolean isAppCertificateLineageFormula() { - return false; - } - - @Override - public boolean isInstallerFormula() { - return false; - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return super.equals(obj); - } - - @NonNull - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - @Override - public String toString() { - return super.toString(); - } - - @Override - protected void finalize() throws Throwable { - super.finalize(); - } - }; - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java index 4a43c2e6c180..9d7b6a171bd4 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java @@ -977,11 +977,19 @@ public final class BackgroundInstallControlServiceTest { assertEquals(USER_ID_1, UserHandle.getUserId(uid)); mPackageListObserver.onPackageRemoved(PACKAGE_NAME_1, uid); + // Test that notifyAllCallbacks doesn't trigger for non-background-installed package + mPackageListObserver.onPackageRemoved(PACKAGE_NAME_3, uid); mTestLooper.dispatchAll(); assertEquals(1, packages.size()); assertFalse(packages.contains(USER_ID_1, PACKAGE_NAME_1)); assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2)); + + verify(mCallbackHelper) + .notifyAllCallbacks( + USER_ID_1, + PACKAGE_NAME_1, + BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL); } @Test 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..0f7de7d78ccf 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -28,7 +28,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; @@ -198,8 +197,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 +432,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 +577,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 +600,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 +623,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); 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..d33317a7168e 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; @@ -336,12 +335,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 +365,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; @@ -14365,9 +14363,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 +14382,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 +14405,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( 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 ad11c2681779..25a8db6e6b9c 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -123,51 +123,7 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { {"MEDIA_PLAY_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE}, KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY, - KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0}, - {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER, - KeyEvent.KEYCODE_B, META_ON}, - {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER, - KeyEvent.KEYCODE_EXPLORER, 0}, - {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS, - KeyEvent.KEYCODE_C, META_ON}, - {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS, - KeyEvent.KEYCODE_CONTACTS, 0}, - {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL, - KeyEvent.KEYCODE_E, META_ON}, - {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL, - KeyEvent.KEYCODE_ENVELOPE, 0}, - {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR, - KeyEvent.KEYCODE_K, META_ON}, - {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR, - KeyEvent.KEYCODE_CALENDAR, 0}, - {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC, - KeyEvent.KEYCODE_P, META_ON}, - {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC, - KeyEvent.KEYCODE_MUSIC, 0}, - {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR, - KeyEvent.KEYCODE_U, META_ON}, - {"CALCULATOR key -> Launch Default Calculator", - new int[]{KeyEvent.KEYCODE_CALCULATOR}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR, - KeyEvent.KEYCODE_CALCULATOR, 0}, - {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS, - KeyEvent.KEYCODE_M, META_ON}, - {"Meta + S -> Launch Default Messaging App", - new int[]{META_KEY, KeyEvent.KEYCODE_S}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING, - KeyEvent.KEYCODE_S, META_ON}}; + KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0}}; } @Keep @@ -295,7 +251,51 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN}, KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE, KeyEvent.KEYCODE_DPAD_DOWN, - META_ON | CTRL_ON}}; + META_ON | CTRL_ON}, + {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER, + KeyEvent.KEYCODE_B, META_ON}, + {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER, + KeyEvent.KEYCODE_EXPLORER, 0}, + {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS, + KeyEvent.KEYCODE_C, META_ON}, + {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS, + KeyEvent.KEYCODE_CONTACTS, 0}, + {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL, + KeyEvent.KEYCODE_E, META_ON}, + {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL, + KeyEvent.KEYCODE_ENVELOPE, 0}, + {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR, + KeyEvent.KEYCODE_K, META_ON}, + {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR, + KeyEvent.KEYCODE_CALENDAR, 0}, + {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC, + KeyEvent.KEYCODE_P, META_ON}, + {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC, + KeyEvent.KEYCODE_MUSIC, 0}, + {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR, + KeyEvent.KEYCODE_U, META_ON}, + {"CALCULATOR key -> Launch Default Calculator", + new int[]{KeyEvent.KEYCODE_CALCULATOR}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR, + KeyEvent.KEYCODE_CALCULATOR, 0}, + {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS, + KeyEvent.KEYCODE_M, META_ON}, + {"Meta + S -> Launch Default Messaging App", + new int[]{META_KEY, KeyEvent.KEYCODE_S}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING, + KeyEvent.KEYCODE_S, META_ON}}; } @Keep @@ -331,6 +331,7 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { mPhoneWindowManager.setupAssistForLaunch(); mPhoneWindowManager.overrideTogglePanel(); mPhoneWindowManager.overrideInjectKeyEvent(); + mPhoneWindowManager.overrideRoleManager(); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index d4ba3b25178d..9e7575f1c644 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -538,7 +538,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { public void testConsecutiveLaunchNewTask() { final IBinder launchCookie = mock(IBinder.class); final WindowContainerToken launchRootTask = mock(WindowContainerToken.class); - mTrampolineActivity.noDisplay = true; + mTrampolineActivity.setIsNoDisplay(true); mTrampolineActivity.mLaunchCookie = launchCookie; mTrampolineActivity.mLaunchRootTask = launchRootTask; onActivityLaunched(mTrampolineActivity); diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java index eacb8e9d628d..a0c5b54603f9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java @@ -25,7 +25,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.window.flags.Flags.explicitRefreshRateHints; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -190,14 +189,9 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( eq(appWindow.getSurfaceControl()), anyFloat(), eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS)); - if (explicitRefreshRateHints()) { - verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( - appWindow.getSurfaceControl(), - SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); - } else { - verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( - any(SurfaceControl.class), anyInt()); - } + verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( + appWindow.getSurfaceControl(), + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); } @Test @@ -226,14 +220,9 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( eq(appWindow.getSurfaceControl()), anyFloat(), eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS)); - if (explicitRefreshRateHints()) { - verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( - appWindow.getSurfaceControl(), - SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); - } else { - verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( - any(SurfaceControl.class), anyInt()); - } + verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( + appWindow.getSurfaceControl(), + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); } @Test @@ -288,14 +277,9 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( appWindow.getSurfaceControl(), 60, Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS); - if (explicitRefreshRateHints()) { - verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( - appWindow.getSurfaceControl(), - SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); - } else { - verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( - any(SurfaceControl.class), anyInt()); - } + verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( + appWindow.getSurfaceControl(), + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); } @Test @@ -352,13 +336,8 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( appWindow.getSurfaceControl(), 60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS); - if (explicitRefreshRateHints()) { - verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( - appWindow.getSurfaceControl(), - SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); - } else { - verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( - any(SurfaceControl.class), anyInt()); - } + verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( + appWindow.getSurfaceControl(), + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java index 3fa38bfe7185..3d08ca2905f3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java @@ -21,14 +21,11 @@ import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; -import static com.android.window.flags.Flags.explicitRefreshRateHints; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.hardware.display.DisplayManager; @@ -36,7 +33,6 @@ import android.os.Parcel; import android.platform.test.annotations.Presubmit; import android.view.Display.Mode; import android.view.Surface; -import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import androidx.test.filters.SmallTest; @@ -274,97 +270,6 @@ public class RefreshRatePolicyTest extends WindowTestsBase { } @Test - public void testAnimatingAppOverridePreferredModeId() { - final WindowState overrideWindow = createWindow("overrideWindow"); - overrideWindow.mAttrs.packageName = "com.android.test"; - overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID; - parcelLayoutParams(overrideWindow); - assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow)); - assertTrue(mPolicy.updateFrameRateVote(overrideWindow)); - assertEquals(FRAME_RATE_VOTE_LOW_EXACT, overrideWindow.mFrameRateVote); - assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE); - assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE); - - if (explicitRefreshRateHints()) { - return; - } - overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation( - overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class), - false /* hidden */, ANIMATION_TYPE_APP_TRANSITION); - assertEquals(0, mPolicy.getPreferredModeId(overrideWindow)); - assertTrue(mPolicy.updateFrameRateVote(overrideWindow)); - assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote); - assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE); - assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE); - - // Use default mode if it is animating by shell transition. - overrideWindow.mActivityRecord.mSurfaceAnimator.cancelAnimation(); - registerTestTransitionPlayer(); - final Transition transition = overrideWindow.mTransitionController.createTransition( - WindowManager.TRANSIT_OPEN); - transition.collect(overrideWindow.mActivityRecord); - assertEquals(0, mPolicy.getPreferredModeId(overrideWindow)); - - // If there will be display size change when switching from preferred mode to default mode, - // then keep the current preferred mode during animating. - mDisplayInfo = spy(mDisplayInfo); - final Mode defaultMode = new Mode(4321 /* width */, 1234 /* height */, LOW_REFRESH_RATE); - doReturn(defaultMode).when(mDisplayInfo).getDefaultMode(); - mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist); - assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow)); - } - - @Test - public void testAnimatingAppOverridePreferredRefreshRate() { - final WindowState overrideWindow = createWindow("overrideWindow"); - overrideWindow.mAttrs.packageName = "com.android.test"; - overrideWindow.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE; - parcelLayoutParams(overrideWindow); - assertEquals(0, mPolicy.getPreferredModeId(overrideWindow)); - assertTrue(mPolicy.updateFrameRateVote(overrideWindow)); - assertEquals(FRAME_RATE_VOTE_LOW_PREFERRED, overrideWindow.mFrameRateVote); - assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE); - assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE); - - if (explicitRefreshRateHints()) { - return; - } - overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation( - overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class), - false /* hidden */, ANIMATION_TYPE_APP_TRANSITION); - assertEquals(0, mPolicy.getPreferredModeId(overrideWindow)); - assertTrue(mPolicy.updateFrameRateVote(overrideWindow)); - assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote); - assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE); - assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE); - } - - @Test - public void testAnimatingDenylist() { - final WindowState window = createWindow("overrideWindow"); - window.mAttrs.packageName = "com.android.test"; - parcelLayoutParams(window); - when(mDenylist.isDenylisted("com.android.test")).thenReturn(true); - assertEquals(0, mPolicy.getPreferredModeId(window)); - assertTrue(mPolicy.updateFrameRateVote(window)); - assertEquals(FRAME_RATE_VOTE_DENY_LIST, window.mFrameRateVote); - assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE); - assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE); - - if (explicitRefreshRateHints()) { - return; - } - window.mActivityRecord.mSurfaceAnimator.startAnimation( - window.getPendingTransaction(), mock(AnimationAdapter.class), - false /* hidden */, ANIMATION_TYPE_APP_TRANSITION); - assertEquals(0, mPolicy.getPreferredModeId(window)); - assertTrue(mPolicy.updateFrameRateVote(window)); - assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote); - assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE); - assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE); - } - - @Test public void testAnimatingCamera() { final WindowState cameraUsingWindow = createWindow("cameraUsingWindow"); cameraUsingWindow.mAttrs.packageName = "com.android.test"; diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 1c878021c9e9..62a471166c5b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4812,6 +4812,23 @@ public class SizeCompatTests extends WindowTestsBase { assertFalse(mActivity.isResizeable()); assertEquals(maxAspect, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */); assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation()); + + // Activity can opt-out the resizability by component level property. + final ComponentName name = getUniqueComponentName(mContext.getPackageName()); + final PackageManager pm = mContext.getPackageManager(); + spyOn(pm); + final PackageManager.Property property = new PackageManager.Property("propertyName", + true /* value */, name.getPackageName(), name.getClassName()); + try { + doReturn(property).when(pm).getPropertyAsUser( + WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, + name.getPackageName(), name.getClassName(), 0 /* userId */); + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + final ActivityRecord optOutActivity = new ActivityBuilder(mAtm) + .setComponent(name).setTask(mTask).build(); + assertFalse(optOutActivity.isUniversalResizeable()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index 3c921c612705..4568c77204a5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -249,7 +249,7 @@ public class TaskLaunchParamsModifierTests extends ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay); ActivityRecord source = createSourceActivity(freeformDisplay); source.mHandoverLaunchDisplayId = freeformDisplay.mDisplayId; - source.noDisplay = true; + source.setIsNoDisplay(true); assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder() @@ -272,7 +272,7 @@ public class TaskLaunchParamsModifierTests extends ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay); ActivityRecord source = createSourceActivity(freeformDisplay); source.mHandoverTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea(); - source.noDisplay = true; + source.setIsNoDisplay(true); assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder() diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index e8779c2b9ead..039a3ddd3e4f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -51,7 +51,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; -import static com.android.window.flags.Flags.explicitRefreshRateHints; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -1632,6 +1631,8 @@ public class TransitionTests extends WindowTestsBase { transition.collect(taskA); transition.setTransientLaunch(recent, taskA); taskRecent.moveToFront("move-recent-to-front"); + recent.setVisibility(true); + recent.setState(ActivityRecord.State.RESUMED, "test"); // During collecting and playing, the recent is on top so it is visible naturally. // While B needs isTransientVisible to keep visibility because it is occluded by recents. @@ -1644,15 +1645,21 @@ public class TransitionTests extends WindowTestsBase { // Switch to another task. For example, use gesture navigation to switch tasks. taskB.moveToFront("move-b-to-front"); + appB.setVisibility(true); // The previous app (taskA) should be paused first so it loses transient visible. Because // visually it is taskA -> taskB, the pause -> resume order should be the same. assertFalse(controller.isTransientVisible(taskA)); - // Keep the recent visible so there won't be 2 activities pausing at the same time. It is - // to avoid the latency to resume the current top, i.e. appB. - assertTrue(controller.isTransientVisible(taskRecent)); - // The recent is paused after the transient transition is finished. - controller.finishTransition(ActionChain.testFinish(transition)); + // The recent is occluded by appB. assertFalse(controller.isTransientVisible(taskRecent)); + // Active transient launch won't be paused if the transition is not finished. It is to + // avoid the latency to resume the current top (appB) by waiting for both recent and appA + // to complete pause. + assertEquals(recent, taskRecent.getResumedActivity()); + assertFalse(taskRecent.startPausing(false /* uiSleeping */, appB /* resuming */, "test")); + // ActivityRecord#makeInvisible will add the invisible recent to the stopping list. + // So when the transition finished, the recent can still be notified to pause and stop. + mDisplayContent.ensureActivitiesVisible(null /* starting */, true /* notifyClients */); + assertTrue(mSupervisor.mStoppingActivities.contains(recent)); } @Test @@ -2883,17 +2890,14 @@ public class TransitionTests extends WindowTestsBase { @Test public void testTransitionsTriggerPerformanceHints() { - final boolean explicitRefreshRateHints = explicitRefreshRateHints(); final var session = new SystemPerformanceHinter.HighPerfSession[1]; - if (explicitRefreshRateHints) { - final SystemPerformanceHinter perfHinter = mWm.mSystemPerformanceHinter; - spyOn(perfHinter); - doAnswer(invocation -> { - session[0] = (SystemPerformanceHinter.HighPerfSession) invocation.callRealMethod(); - spyOn(session[0]); - return session[0]; - }).when(perfHinter).createSession(anyInt(), anyInt(), anyString()); - } + final SystemPerformanceHinter perfHinter = mWm.mSystemPerformanceHinter; + spyOn(perfHinter); + doAnswer(invocation -> { + session[0] = (SystemPerformanceHinter.HighPerfSession) invocation.callRealMethod(); + spyOn(session[0]); + return session[0]; + }).when(perfHinter).createSession(anyInt(), anyInt(), anyString()); final TransitionController controller = mDisplayContent.mTransitionController; final TestTransitionPlayer player = registerTestTransitionPlayer(); final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build(); @@ -2905,15 +2909,11 @@ public class TransitionTests extends WindowTestsBase { player.start(); verify(mDisplayContent).enableHighPerfTransition(true); - if (explicitRefreshRateHints) { - verify(session[0]).start(); - } + verify(session[0]).start(); player.finish(); verify(mDisplayContent).enableHighPerfTransition(false); - if (explicitRefreshRateHints) { - verify(session[0]).close(); - } + verify(session[0]).close(); } @Test diff --git a/services/usb/OWNERS b/services/usb/OWNERS index d35dbb56437b..2dff392d4e34 100644 --- a/services/usb/OWNERS +++ b/services/usb/OWNERS @@ -1,9 +1,9 @@ -aprasath@google.com -kumarashishg@google.com -sarup@google.com anothermark@google.com +febinthattil@google.com +aprasath@google.com badhri@google.com elaurent@google.com albertccwang@google.com jameswei@google.com -howardyen@google.com
\ No newline at end of file +howardyen@google.com +kumarashishg@google.com
\ No newline at end of file diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java index 45a7fafa90a7..07969bd2cd43 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java @@ -304,11 +304,17 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo @Override public void unloadModel(int modelHandle) { synchronized (SoundTriggerModule.this) { - int sessionId; checkValid(); - sessionId = mLoadedModels.get(modelHandle).unload(); - mAudioSessionProvider.releaseSession(sessionId); + final var session = mLoadedModels.get(modelHandle).getSession(); + mLoadedModels.remove(modelHandle); + mAudioSessionProvider.releaseSession(session.mSessionHandle); } + // We don't need to post-synchronize on anything once the HAL has finished the unload + // and dispatched any appropriate callbacks -- since we don't do any state checking + // onModelUnloaded regardless. + // This is generally safe since there is no post-condition on the framework side when + // a model is unloaded. We assume that we won't ever have a modelHandle collision. + mHalService.unloadSoundModel(modelHandle); } @Override @@ -402,6 +408,10 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo return mState; } + private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession getSession() { + return mSession; + } + private void setState(@NonNull ModelState state) { mState = state; SoundTriggerModule.this.notifyAll(); @@ -426,16 +436,6 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo return mHandle; } - /** - * Unloads the model. - * @return The audio session handle. - */ - private int unload() { - mHalService.unloadSoundModel(mHandle); - mLoadedModels.remove(mHandle); - return mSession.mSessionHandle; - } - private IBinder startRecognition(@NonNull RecognitionConfig config) { if (mIsStopping == true) { throw new RecoverableException(Status.INTERNAL_ERROR, "Race occurred"); diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index 9b83719402a0..be34619acac1 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -47,8 +47,6 @@ import android.telephony.TelephonyManager; import android.util.Log; import android.util.SparseArray; -import com.android.internal.annotations.VisibleForTesting; - import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -538,7 +536,13 @@ public final class SmsApplication { } private static String getDefaultSmsPackage(Context context, int userId) { - return context.getSystemService(RoleManager.class).getSmsRoleHolder(userId); + // RoleManager might be null in unit tests running older mockito versions that do not + // support mocking final classes. + RoleManager roleManager = context.getSystemService(RoleManager.class); + if (roleManager == null) { + return ""; + } + return roleManager.getSmsRoleHolder(userId); } /** diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 0808cc3f1a75..2a06c3da0195 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -10153,6 +10153,15 @@ public class CarrierConfigManager { "satellite_roaming_esos_inactivity_timeout_sec_int"; /** + * A string array containing the list of messaging package names that support satellite. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final String KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY = + "satellite_supported_msg_apps_string_array"; + + /** * Indicating whether DUN APN should be disabled when the device is roaming. In that case, * the default APN (i.e. internet) will be used for tethering. * diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java b/telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl index 60cfc4876414..9a6f6b821d28 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java +++ b/telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * 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. @@ -14,19 +14,17 @@ * limitations under the License. */ -package com.android.server.integrity.serializer; - -import android.annotation.NonNull; +package android.telephony.satellite; /** - * Thrown when rule serialization fails. + * Interface for satellite disallowed reason change callback. + * + * @hide */ -public class RuleSerializeException extends Exception { - public RuleSerializeException(@NonNull String message) { - super(message); - } - - public RuleSerializeException(@NonNull String message, @NonNull Throwable cause) { - super(message, cause); - } +oneway interface ISatelliteDisallowedReasonsCallback { + /** + * Indicates that disallowed reason of satellite has changed. + * @param disallowedReasons list of disallowed reasons. + */ + void onSatelliteDisallowedReasonsChanged(in int[] disallowedReasons); } diff --git a/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java new file mode 100644 index 000000000000..5e276aa49b05 --- /dev/null +++ b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.satellite; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; + +import com.android.internal.telephony.flags.Flags; + +/** + * A callback class for disallowed reason of satellite change events. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +public interface SatelliteDisallowedReasonsCallback { + + /** + * Called when disallowed reason of satellite has changed. + * @param disallowedReasons Integer array of disallowed reasons. + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + void onSatelliteDisallowedReasonsChanged(@NonNull int[] disallowedReasons); +} diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index be02232abe1b..7be3f337e43a 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -104,6 +104,11 @@ public final class SatelliteManager { sSatelliteCommunicationAllowedStateCallbackMap = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap<SatelliteDisallowedReasonsCallback, + ISatelliteDisallowedReasonsCallback> + sSatelliteDisallowedReasonsCallbackMap = + new ConcurrentHashMap<>(); + private final int mSubId; /** @@ -1487,6 +1492,47 @@ public final class SatelliteManager { public @interface SatelliteCommunicationRestrictionReason {} /** + * Satellite is disallowed because it is not supported. + * @hide + */ + public static final int SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED = 0; + + /** + * Satellite is disallowed because it has not been provisioned. + * @hide + */ + public static final int SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED = 1; + + /** + * Satellite is disallowed because it is currently outside an allowed region. + * @hide + */ + public static final int SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION = 2; + + /** + * Satellite is disallowed because an unsupported default message application is being used. + * @hide + */ + public static final int SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP = 3; + + /** + * Satellite is disallowed because location settings have been disabled. + * @hide + */ + public static final int SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED = 4; + + /** @hide */ + @IntDef(prefix = "SATELLITE_DISALLOWED_REASON_", value = { + SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED, + SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED, + SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION, + SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP, + SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SatelliteDisallowedReason {} + + /** * Start receiving satellite transmission updates. * This can be called by the pointing UI when the user starts pointing to the satellite. * Modem should continue to report the pointing input as the device or satellite moves. @@ -2579,6 +2625,119 @@ public final class SatelliteManager { } /** + * Returns list of disallowed reasons of satellite. + * + * @return list of disallowed reasons of satellite. + * + * @throws SecurityException if caller doesn't have required permission. + * @throws IllegalStateException if Telephony process isn't available. + * @hide + */ + @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @SatelliteDisallowedReason + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @NonNull + public List<Integer> getSatelliteDisallowedReasons() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + int[] receivedArray = telephony.getSatelliteDisallowedReasons(); + if (receivedArray.length == 0) { + logd("receivedArray is empty, create empty list"); + return new ArrayList<>(); + } else { + return Arrays.stream(receivedArray).boxed().collect(Collectors.toList()); + } + } else { + throw new IllegalStateException("Telephony service is null."); + } + } catch (RemoteException ex) { + loge("getSatelliteDisallowedReasons() RemoteException: " + ex); + ex.rethrowAsRuntimeException(); + } + return new ArrayList<>(); + } + + /** + * Registers for disallowed reasons change event from satellite service. + * + * @param executor The executor on which the callback will be called. + * @param callback The callback to handle disallowed reasons changed event. + * + * @throws SecurityException if caller doesn't have required permission. + * @throws IllegalStateException if Telephony process is not available. + * @hide + */ + @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public void registerForSatelliteDisallowedReasonsChanged( + @NonNull @CallbackExecutor Executor executor, + @NonNull SatelliteDisallowedReasonsCallback callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + ISatelliteDisallowedReasonsCallback internalCallback = + new ISatelliteDisallowedReasonsCallback.Stub() { + @Override + public void onSatelliteDisallowedReasonsChanged( + int[] disallowedReasons) { + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> callback.onSatelliteDisallowedReasonsChanged( + disallowedReasons))); + } + }; + telephony.registerForSatelliteDisallowedReasonsChanged(internalCallback); + sSatelliteDisallowedReasonsCallbackMap.put(callback, internalCallback); + } else { + throw new IllegalStateException("Telephony service is null."); + } + } catch (RemoteException ex) { + loge("registerForSatelliteDisallowedReasonsChanged() RemoteException" + ex); + ex.rethrowAsRuntimeException(); + } + } + + /** + * Unregisters for disallowed reasons change event from satellite service. + * + * @param callback The callback that was passed to + * {@link #registerForSatelliteDisallowedReasonsChanged( + * Executor, SatelliteDisallowedReasonsCallback)} + * + * @throws SecurityException if caller doesn't have required permission. + * @throws IllegalStateException if Telephony process is not available. + * @hide + */ + @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public void unregisterForSatelliteDisallowedReasonsChanged( + @NonNull SatelliteDisallowedReasonsCallback callback) { + Objects.requireNonNull(callback); + ISatelliteDisallowedReasonsCallback internalCallback = + sSatelliteDisallowedReasonsCallbackMap.remove(callback); + + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + if (internalCallback != null) { + telephony.unregisterForSatelliteDisallowedReasonsChanged(internalCallback); + } else { + loge("unregisterForSatelliteDisallowedReasonsChanged: No internal callback."); + throw new IllegalArgumentException("callback is not valid"); + } + } else { + throw new IllegalStateException("Telephony service is null."); + } + } catch (RemoteException ex) { + loge("unregisterForSatelliteDisallowedReasonsChanged() RemoteException: " + ex); + ex.rethrowAsRuntimeException(); + } + } + + /** * Request to get the signal strength of the satellite connection. * * <p> diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 231c8f551389..62cbb02c9fc7 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -71,6 +71,7 @@ import android.telephony.satellite.INtnSignalStrengthCallback; import android.telephony.satellite.ISatelliteCapabilitiesCallback; import android.telephony.satellite.ISatelliteCommunicationAllowedStateCallback; import android.telephony.satellite.ISatelliteDatagramCallback; +import android.telephony.satellite.ISatelliteDisallowedReasonsCallback; import android.telephony.satellite.ISatelliteTransmissionUpdateCallback; import android.telephony.satellite.ISatelliteProvisionStateCallback; import android.telephony.satellite.ISatelliteSupportedStateCallback; @@ -2955,6 +2956,37 @@ interface ITelephony { in boolean needFullScreenPointingUI, IIntegerConsumer callback); /** + * Returns integer array of disallowed reasons of satellite. + * + * @return Integer array of disallowed reasons of satellite. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.SATELLITE_COMMUNICATION)") + int[] getSatelliteDisallowedReasons(); + + /** + * Registers for disallowed reasons change event from satellite service. + * + * @param callback The callback to handle disallowed reasons changed event. + * + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.SATELLITE_COMMUNICATION)") + void registerForSatelliteDisallowedReasonsChanged( + ISatelliteDisallowedReasonsCallback callback); + + /** + * Unregisters for disallowed reasons change event from satellite service. + * If callback was not registered before, the request will be ignored. + * + * @param callback The callback to handle disallowed reasons changed event. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.SATELLITE_COMMUNICATION)") + void unregisterForSatelliteDisallowedReasonsChanged( + ISatelliteDisallowedReasonsCallback callback); + + /** * Request to get whether satellite communication is allowed for the current location. * * @param subId The subId of the subscription to get whether satellite communication is allowed diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java new file mode 100644 index 000000000000..a3e5533599bc --- /dev/null +++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java @@ -0,0 +1,156 @@ +/* + * 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.app.jank.tests; + +import static org.junit.Assert.assertEquals; + +import android.app.jank.Flags; +import android.app.jank.JankTracker; +import android.app.jank.StateTracker; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.view.Choreographer; +import android.view.View; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.core.app.ActivityScenario; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + +@RunWith(AndroidJUnit4.class) +public class JankTrackerTest { + private Choreographer mChoreographer; + private JankTracker mJankTracker; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + /** + * Start an empty activity so decore view is not null when creating the JankTracker instance. + */ + private static ActivityScenario<EmptyActivity> sEmptyActivityRule; + + private static String sActivityName; + + private static View sActivityDecorView; + + @BeforeClass + public static void classSetup() { + sEmptyActivityRule = ActivityScenario.launch(EmptyActivity.class); + sEmptyActivityRule.onActivity(activity -> { + sActivityDecorView = activity.getWindow().getDecorView(); + sActivityName = activity.toString(); + }); + } + + @AfterClass + public static void classTearDown() { + sEmptyActivityRule.close(); + } + + @Before + @UiThreadTest + public void setup() { + mChoreographer = Choreographer.getInstance(); + mJankTracker = new JankTracker(mChoreographer, sActivityDecorView); + mJankTracker.setActivityName(sActivityName); + } + + /** + * When jank tracking is enabled the activity name should be added as a state to associate + * frames to it. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void jankTracking_WhenEnabled_ActivityAdded() { + mJankTracker.enableAppJankTracking(); + + ArrayList<StateTracker.StateData> stateData = new ArrayList<>(); + mJankTracker.getAllUiStates(stateData); + + assertEquals(1, stateData.size()); + + StateTracker.StateData firstState = stateData.getFirst(); + + assertEquals(sActivityName, firstState.mWidgetId); + } + + /** + * No states should be added when tracking is disabled. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void jankTrackingDisabled_StatesShouldNot_BeAddedToTracker() { + mJankTracker.disableAppJankTracking(); + + mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID", + "FAKE_STATE"); + + ArrayList<StateTracker.StateData> stateData = new ArrayList<>(); + mJankTracker.getAllUiStates(stateData); + + assertEquals(0, stateData.size()); + } + + /** + * The activity name as well as the test state should be added for frame association. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void jankTrackingEnabled_StatesShould_BeAddedToTracker() { + mJankTracker.forceListenerRegistration(); + + mJankTracker.enableAppJankTracking(); + mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID", + "FAKE_STATE"); + + ArrayList<StateTracker.StateData> stateData = new ArrayList<>(); + mJankTracker.getAllUiStates(stateData); + + assertEquals(2, stateData.size()); + } + + /** + * Activity state should only be added once even if jank tracking is enabled multiple times. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void jankTrackingEnabled_EnabledCalledTwice_ActivityStateOnlyAddedOnce() { + mJankTracker.enableAppJankTracking(); + + ArrayList<StateTracker.StateData> stateData = new ArrayList<>(); + mJankTracker.getAllUiStates(stateData); + + assertEquals(1, stateData.size()); + + stateData.clear(); + + mJankTracker.enableAppJankTracking(); + mJankTracker.getAllUiStates(stateData); + + assertEquals(1, stateData.size()); + } +} diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt index 1c6bd1114c1c..332b9b832037 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt @@ -132,6 +132,24 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() } + private fun getMinimizeButtonForTheApp(caption: UiObject2?): UiObject2 { + return caption + ?.children + ?.find { it.resourceName.endsWith(MINIMIZE_BUTTON_VIEW) } + ?: error("Unable to find resource $MINIMIZE_BUTTON_VIEW\n") + } + + fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) { + val caption = getCaptionForTheApp(wmHelper, device) + val minimizeButton = getMinimizeButtonForTheApp(caption) + minimizeButton.click() + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withWindowSurfaceDisappeared(innerHelper) + .waitForAndVerify() + } + /** Open maximize menu and click snap resize button on the app header for the given app. */ fun snapResizeDesktopApp( wmHelper: WindowManagerStateHelper, @@ -400,6 +418,7 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : const val DESKTOP_MODE_BUTTON: String = "desktop_button" const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button" const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button" + const val MINIMIZE_BUTTON_VIEW: String = "minimize_window" val caption: BySelector get() = By.res(SYSTEMUI_PACKAGE, CAPTION) } diff --git a/tests/Input/res/xml/bookmarks.xml b/tests/Input/res/xml/bookmarks.xml new file mode 100644 index 000000000000..ba3f1871cdec --- /dev/null +++ b/tests/Input/res/xml/bookmarks.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<bookmarks> + <!-- the key combinations for the following shortcuts must be in sync + with the key combinations sent by the test in KeyGestureControllerTests.java --> + <bookmark + role="android.app.role.BROWSER" + shortcut="b" /> + <bookmark + category="android.intent.category.APP_CONTACTS" + shortcut="c" /> + <bookmark + category="android.intent.category.APP_EMAIL" + shortcut="e" /> + <bookmark + category="android.intent.category.APP_CALENDAR" + shortcut="k" /> + <bookmark + category="android.intent.category.APP_MAPS" + shortcut="m" /> + <bookmark + category="android.intent.category.APP_MUSIC" + shortcut="p" /> + <bookmark + role="android.app.role.SMS" + shortcut="s" /> + <bookmark + category="android.intent.category.APP_CALCULATOR" + shortcut="u" /> + + <bookmark + role="android.app.role.BROWSER" + shortcut="b" + shift="true" /> + + <bookmark + category="android.intent.category.APP_CONTACTS" + shortcut="c" + shift="true" /> + + <bookmark + package="com.test" + class="com.test.BookmarkTest" + shortcut="j" + shift="true" /> +</bookmarks>
\ No newline at end of file diff --git a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt index 01c56b7148cd..862886ce69d2 100644 --- a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt +++ b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt @@ -21,6 +21,7 @@ import android.hardware.input.InputManager import android.hardware.input.KeyGestureEvent import android.platform.test.annotations.Presubmit import android.view.KeyEvent +import androidx.test.core.app.ApplicationProvider import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -42,7 +43,7 @@ class InputGestureManagerTests { @Before fun setup() { - inputGestureManager = InputGestureManager() + inputGestureManager = InputGestureManager(ApplicationProvider.getApplicationContext()) } @Test diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index f2d3229a098c..6eb00457a1a6 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -95,8 +95,11 @@ class InputManagerServiceTests { @get:Rule val extendedMockitoRule = - ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java) - .mockStatic(PermissionChecker::class.java).build()!! + ExtendedMockitoRule.Builder(this) + .mockStatic(LocalServices::class.java) + .mockStatic(PermissionChecker::class.java) + .mockStatic(KeyCharacterMap::class.java) + .build()!! @get:Rule val setFlagsRule = SetFlagsRule() @@ -122,6 +125,9 @@ class InputManagerServiceTests { @Mock private lateinit var kbdController: InputManagerService.KeyboardBacklightControllerInterface + @Mock + private lateinit var kcm: KeyCharacterMap + private lateinit var service: InputManagerService private lateinit var localService: InputManagerInternal private lateinit var context: Context @@ -171,6 +177,9 @@ class InputManagerServiceTests { ExtendedMockito.doReturn(packageManagerInternal).`when` { LocalServices.getService(eq(PackageManagerInternal::class.java)) } + ExtendedMockito.doReturn(kcm).`when` { + KeyCharacterMap.load(anyInt()) + } assertTrue("Local service must be registered", this::localService.isInitialized) service.setWindowManagerCallbacks(wmCallbacks) diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index 0b147d63ed08..6c9f764bbdee 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -16,13 +16,16 @@ package com.android.server.input +import android.app.role.RoleManager import android.content.Context import android.content.ContextWrapper +import android.content.Intent import android.content.pm.PackageManager import android.content.res.Resources -import android.hardware.input.IInputManager +import android.content.res.XmlResourceParser import android.hardware.input.AidlKeyGestureEvent import android.hardware.input.AppLaunchData +import android.hardware.input.IInputManager import android.hardware.input.IKeyGestureEventListener import android.hardware.input.IKeyGestureHandler import android.hardware.input.InputGestureData @@ -34,14 +37,15 @@ import android.os.Process import android.os.SystemClock import android.os.SystemProperties import android.os.test.TestLooper -import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.Presubmit import android.platform.test.flag.junit.SetFlagsRule import android.view.InputDevice +import android.view.KeyCharacterMap import android.view.KeyEvent import android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE import androidx.test.core.app.ApplicationProvider +import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.R import com.android.internal.annotations.Keep import com.android.internal.util.FrameworkStatsLog @@ -99,7 +103,9 @@ class KeyGestureControllerTests { @Rule val extendedMockitoRule = ExtendedMockitoRule.Builder(this) .mockStatic(FrameworkStatsLog::class.java) - .mockStatic(SystemProperties::class.java).build()!! + .mockStatic(SystemProperties::class.java) + .mockStatic(KeyCharacterMap::class.java) + .build()!! @JvmField @Rule @@ -116,6 +122,7 @@ class KeyGestureControllerTests { private var currentPid = 0 private lateinit var context: Context + private lateinit var keyGestureController: KeyGestureController private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession private lateinit var testLooper: TestLooper private var events = mutableListOf<KeyGestureEvent>() @@ -123,8 +130,6 @@ class KeyGestureControllerTests { @Before fun setup() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - Mockito.`when`(context.resources).thenReturn(resources) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) setupInputDevices() setupBehaviors() testLooper = TestLooper() @@ -139,11 +144,13 @@ class KeyGestureControllerTests { } private fun setupBehaviors() { - Mockito.`when`( - resources.getBoolean( - com.android.internal.R.bool.config_enableScreenshotChord - ) - ).thenReturn(true) + Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1") + Mockito.`when`(resources.getBoolean(R.bool.config_enableScreenshotChord)).thenReturn(true) + val testBookmarks: XmlResourceParser = context.resources.getXml( + com.android.test.input.R.xml.bookmarks + ) + Mockito.`when`(resources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks) + Mockito.`when`(context.resources).thenReturn(resources) Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) .thenReturn(true) Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) @@ -152,6 +159,10 @@ class KeyGestureControllerTests { } private fun setupInputDevices() { + val correctIm = context.getSystemService(InputManager::class.java)!! + val virtualDevice = correctIm.getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD)!! + val kcm = virtualDevice.keyCharacterMap!! + inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) val inputManager = InputManager(context) Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) @@ -159,9 +170,17 @@ class KeyGestureControllerTests { val keyboardDevice = InputDevice.Builder().setId(DEVICE_ID).build() Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice) + ExtendedMockito.`when`(KeyCharacterMap.load(Mockito.anyInt())).thenReturn(kcm) } - private fun notifyHomeGestureCompleted(keyGestureController: KeyGestureController) { + private fun setupKeyGestureController() { + keyGestureController = KeyGestureController(context, testLooper.looper) + Mockito.`when`(iInputManager.getAppLaunchBookmarks()) + .thenReturn(keyGestureController.appLaunchBookmarks) + keyGestureController.systemRunning() + } + + private fun notifyHomeGestureCompleted() { keyGestureController.notifyKeyGestureCompleted( DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H), KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON, @@ -171,12 +190,12 @@ class KeyGestureControllerTests { @Test fun testKeyGestureEvent_registerUnregisterListener() { - val keyGestureController = KeyGestureController(context, testLooper.looper) + setupKeyGestureController() val listener = KeyGestureEventListener() // Register key gesture event listener keyGestureController.registerKeyGestureEventListener(listener, 0) - notifyHomeGestureCompleted(keyGestureController) + notifyHomeGestureCompleted() testLooper.dispatchAll() assertEquals( "Listener should get callbacks on key gesture event completed", @@ -192,7 +211,7 @@ class KeyGestureControllerTests { // Unregister listener events.clear() keyGestureController.unregisterKeyGestureEventListener(listener, 0) - notifyHomeGestureCompleted(keyGestureController) + notifyHomeGestureCompleted() testLooper.dispatchAll() assertEquals( "Listener should not get callback after being unregistered", @@ -203,7 +222,7 @@ class KeyGestureControllerTests { @Test fun testKeyGestureEvent_multipleGestureHandlers() { - val keyGestureController = KeyGestureController(context, testLooper.looper) + setupKeyGestureController() // Set up two callbacks. var callbackCount1 = 0 @@ -267,7 +286,7 @@ class KeyGestureControllerTests { } @Keep - private fun keyGestureEventHandlerTestArguments(): Array<TestData> { + private fun systemGesturesTestArguments(): Array<TestData> { return arrayOf( TestData( "META + A -> Launch Assistant", @@ -278,25 +297,6 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "RECENT_APPS -> Show Overview", - intArrayOf(KeyEvent.KEYCODE_RECENT_APPS), - KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, - intArrayOf(KeyEvent.KEYCODE_RECENT_APPS), - 0, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( - "APP_SWITCH -> App Switch", - intArrayOf(KeyEvent.KEYCODE_APP_SWITCH), - KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, - intArrayOf(KeyEvent.KEYCODE_APP_SWITCH), - 0, - intArrayOf( - KeyGestureEvent.ACTION_GESTURE_START, - KeyGestureEvent.ACTION_GESTURE_COMPLETE - ) - ), - TestData( "META + H -> Go Home", intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_H), KeyGestureEvent.KEY_GESTURE_TYPE_HOME, @@ -465,6 +465,379 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( + "META + ALT -> Toggle Caps Lock", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "ALT + META -> Toggle Caps Lock", + intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_META_LEFT), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + TAB -> Open Overview", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_TAB), + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, + intArrayOf(KeyEvent.KEYCODE_TAB), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "ALT + TAB -> Toggle Recent Apps Switcher", + intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_TAB), + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER, + intArrayOf(KeyEvent.KEYCODE_TAB), + KeyEvent.META_ALT_ON, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_START, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + TestData( + "CTRL + SPACE -> Switch Language Forward", + intArrayOf(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_SPACE), + KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, + intArrayOf(KeyEvent.KEYCODE_SPACE), + KeyEvent.META_CTRL_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "CTRL + SHIFT + SPACE -> Switch Language Backward", + intArrayOf( + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent.KEYCODE_SPACE + ), + KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, + intArrayOf(KeyEvent.KEYCODE_SPACE), + KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "CTRL + ALT + Z -> Accessibility Shortcut", + intArrayOf( + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_Z + ), + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT, + intArrayOf(KeyEvent.KEYCODE_Z), + KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + B -> Launch Default Browser", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_B), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_B), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER) + ), + TestData( + "META + C -> Launch Default Contacts", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_C), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS) + ), + TestData( + "META + E -> Launch Default Email", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_E), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_E), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL) + ), + TestData( + "META + K -> Launch Default Calendar", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_K), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_K), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR) + ), + TestData( + "META + M -> Launch Default Maps", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_M), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_M), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MAPS) + ), + TestData( + "META + P -> Launch Default Music", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_P), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC) + ), + TestData( + "META + S -> Launch Default SMS", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_S), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_S), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_SMS) + ), + TestData( + "META + U -> Launch Default Calculator", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_U), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_U), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR) + ), + TestData( + "META + SHIFT + B -> Launch Default Browser", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent.KEYCODE_B + ), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_B), + KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER) + ), + TestData( + "META + SHIFT + C -> Launch Default Contacts", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent.KEYCODE_C + ), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_C), + KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS) + ), + TestData( + "META + SHIFT + J -> Launch Target Activity", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent.KEYCODE_J + ), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_J), + KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest") + ), + TestData( + "META + CTRL + DEL -> Trigger Bug Report", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_DEL + ), + KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT, + intArrayOf(KeyEvent.KEYCODE_DEL), + KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "Meta + Alt + 3 -> Toggle Bounce Keys", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_3 + ), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS, + intArrayOf(KeyEvent.KEYCODE_3), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "Meta + Alt + 4 -> Toggle Mouse Keys", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_4 + ), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS, + intArrayOf(KeyEvent.KEYCODE_4), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "Meta + Alt + 5 -> Toggle Sticky Keys", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_5 + ), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS, + intArrayOf(KeyEvent.KEYCODE_5), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "Meta + Alt + 6 -> Toggle Slow Keys", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_6 + ), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS, + intArrayOf(KeyEvent.KEYCODE_6), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + CTRL + D -> Move a task to next display", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_D + ), + KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY, + intArrayOf(KeyEvent.KEYCODE_D), + KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "ALT + [ -> Resizes a task to fit the left half of the screen", + intArrayOf( + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_LEFT_BRACKET + ), + KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW, + intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET), + KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "ALT + ] -> Resizes a task to fit the right half of the screen", + intArrayOf( + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_RIGHT_BRACKET + ), + KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW, + intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET), + KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "ALT + '=' -> Maximizes a task to fit the screen", + intArrayOf( + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_EQUALS + ), + KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW, + intArrayOf(KeyEvent.KEYCODE_EQUALS), + KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "ALT + '-' -> Restores a task size to its previous bounds", + intArrayOf( + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_MINUS + ), + KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE, + intArrayOf(KeyEvent.KEYCODE_MINUS), + KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ) + ) + } + + @Test + @Parameters(method = "systemGesturesTestArguments") + @EnableFlags( + com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS, + com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, + com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS + ) + fun testKeyGestures(test: TestData) { + setupKeyGestureController() + testKeyGestureInternal(test) + } + + @Test + @Parameters(method = "systemGesturesTestArguments") + @EnableFlags( + com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS, + com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, + com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS + ) + fun testCustomKeyGesturesNotAllowedForSystemGestures(test: TestData) { + setupKeyGestureController() + // Need to re-init so that bookmarks are correctly blocklisted + Mockito.`when`(iInputManager.getAppLaunchBookmarks()) + .thenReturn(keyGestureController.appLaunchBookmarks) + keyGestureController.systemRunning() + + val builder = InputGestureData.Builder() + .setKeyGestureType(test.expectedKeyGestureType) + .setTrigger( + InputGestureData.createKeyTrigger( + test.expectedKeys[0], + test.expectedModifierState + ) + ) + if (test.expectedAppLaunchData != null) { + builder.setAppLaunchData(test.expectedAppLaunchData) + } + assertEquals( + test.toString(), + InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE, + keyGestureController.addCustomInputGesture(0, builder.build().aidlData) + ) + } + + @Keep + private fun systemKeysTestArguments(): Array<TestData> { + return arrayOf( + TestData( + "RECENT_APPS -> Show Overview", + intArrayOf(KeyEvent.KEYCODE_RECENT_APPS), + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, + intArrayOf(KeyEvent.KEYCODE_RECENT_APPS), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "APP_SWITCH -> App Switch", + intArrayOf(KeyEvent.KEYCODE_APP_SWITCH), + KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, + intArrayOf(KeyEvent.KEYCODE_APP_SWITCH), + 0, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_START, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + TestData( "BRIGHTNESS_UP -> Brightness Up", intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_UP), KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP, @@ -553,101 +926,88 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "META + ALT -> Toggle Caps Lock", - intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT), - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, - intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT), + "SYSRQ -> Take screenshot", + intArrayOf(KeyEvent.KEYCODE_SYSRQ), + KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, + intArrayOf(KeyEvent.KEYCODE_SYSRQ), 0, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "ALT + META -> Toggle Caps Lock", - intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_META_LEFT), - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, - intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT), + "ESC -> Close All Dialogs", + intArrayOf(KeyEvent.KEYCODE_ESCAPE), + KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS, + intArrayOf(KeyEvent.KEYCODE_ESCAPE), 0, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "META + TAB -> Open Overview", - intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_TAB), - KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, - intArrayOf(KeyEvent.KEYCODE_TAB), - KeyEvent.META_META_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( - "ALT + TAB -> Toggle Recent Apps Switcher", - intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_TAB), - KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER, - intArrayOf(KeyEvent.KEYCODE_TAB), - KeyEvent.META_ALT_ON, - intArrayOf( - KeyGestureEvent.ACTION_GESTURE_START, - KeyGestureEvent.ACTION_GESTURE_COMPLETE - ) + "EXPLORER -> Launch Default Browser", + intArrayOf(KeyEvent.KEYCODE_EXPLORER), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_EXPLORER), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER) ), TestData( - "CTRL + SPACE -> Switch Language Forward", - intArrayOf(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_SPACE), - KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, - intArrayOf(KeyEvent.KEYCODE_SPACE), - KeyEvent.META_CTRL_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + "ENVELOPE -> Launch Default Email", + intArrayOf(KeyEvent.KEYCODE_ENVELOPE), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_ENVELOPE), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL) ), TestData( - "CTRL + SHIFT + SPACE -> Switch Language Backward", - intArrayOf( - KeyEvent.KEYCODE_CTRL_LEFT, - KeyEvent.KEYCODE_SHIFT_LEFT, - KeyEvent.KEYCODE_SPACE - ), - KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, - intArrayOf(KeyEvent.KEYCODE_SPACE), - KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + "CONTACTS -> Launch Default Contacts", + intArrayOf(KeyEvent.KEYCODE_CONTACTS), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_CONTACTS), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS) ), TestData( - "CTRL + ALT + Z -> Accessibility Shortcut", - intArrayOf( - KeyEvent.KEYCODE_CTRL_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_Z - ), - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT, - intArrayOf(KeyEvent.KEYCODE_Z), - KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + "CALENDAR -> Launch Default Calendar", + intArrayOf(KeyEvent.KEYCODE_CALENDAR), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_CALENDAR), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR) ), TestData( - "SYSRQ -> Take screenshot", - intArrayOf(KeyEvent.KEYCODE_SYSRQ), - KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, - intArrayOf(KeyEvent.KEYCODE_SYSRQ), + "MUSIC -> Launch Default Music", + intArrayOf(KeyEvent.KEYCODE_MUSIC), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_MUSIC), 0, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC) ), TestData( - "ESC -> Close All Dialogs", - intArrayOf(KeyEvent.KEYCODE_ESCAPE), - KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS, - intArrayOf(KeyEvent.KEYCODE_ESCAPE), + "CALCULATOR -> Launch Default Calculator", + intArrayOf(KeyEvent.KEYCODE_CALCULATOR), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_CALCULATOR), 0, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR) ), ) } @Test - @Parameters(method = "keyGestureEventHandlerTestArguments") - fun testKeyGestures(test: TestData) { - val keyGestureController = KeyGestureController(context, testLooper.looper) - testKeyGestureInternal(keyGestureController, test) + @Parameters(method = "systemKeysTestArguments") + fun testSystemKeys(test: TestData) { + setupKeyGestureController() + testKeyGestureInternal(test) } @Test fun testKeycodesFullyConsumed_irrespectiveOfHandlers() { - val keyGestureController = KeyGestureController(context, testLooper.looper) + setupKeyGestureController() val testKeys = intArrayOf( KeyEvent.KEYCODE_RECENT_APPS, KeyEvent.KEYCODE_APP_SWITCH, @@ -675,7 +1035,7 @@ class KeyGestureControllerTests { keyGestureController.registerKeyGestureHandler(handler, 0) for (key in testKeys) { - sendKeys(keyGestureController, intArrayOf(key), assertNotSentToApps = true) + sendKeys(intArrayOf(key), assertNotSentToApps = true) } } @@ -683,9 +1043,8 @@ class KeyGestureControllerTests { fun testSearchKeyGestures_defaultSearch() { Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior)) .thenReturn(SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH) - val keyGestureController = KeyGestureController(context, testLooper.looper) + setupKeyGestureController() testKeyGestureNotProduced( - keyGestureController, "SEARCH -> Default Search", intArrayOf(KeyEvent.KEYCODE_SEARCH), ) @@ -695,9 +1054,8 @@ class KeyGestureControllerTests { fun testSearchKeyGestures_searchActivity() { Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior)) .thenReturn(SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY) - val keyGestureController = KeyGestureController(context, testLooper.looper) + setupKeyGestureController() testKeyGestureInternal( - keyGestureController, TestData( "SEARCH -> Launch Search Activity", intArrayOf(KeyEvent.KEYCODE_SEARCH), @@ -713,9 +1071,8 @@ class KeyGestureControllerTests { fun testSettingKeyGestures_doNothing() { Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior)) .thenReturn(SETTINGS_KEY_BEHAVIOR_NOTHING) - val keyGestureController = KeyGestureController(context, testLooper.looper) + setupKeyGestureController() testKeyGestureNotProduced( - keyGestureController, "SETTINGS -> Do Nothing", intArrayOf(KeyEvent.KEYCODE_SETTINGS), ) @@ -725,9 +1082,8 @@ class KeyGestureControllerTests { fun testSettingKeyGestures_settingsActivity() { Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior)) .thenReturn(SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY) - val keyGestureController = KeyGestureController(context, testLooper.looper) + setupKeyGestureController() testKeyGestureInternal( - keyGestureController, TestData( "SETTINGS -> Launch Settings Activity", intArrayOf(KeyEvent.KEYCODE_SETTINGS), @@ -743,9 +1099,8 @@ class KeyGestureControllerTests { fun testSettingKeyGestures_notificationPanel() { Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior)) .thenReturn(SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) - val keyGestureController = KeyGestureController(context, testLooper.looper) + setupKeyGestureController() testKeyGestureInternal( - keyGestureController, TestData( "SETTINGS -> Toggle Notification Panel", intArrayOf(KeyEvent.KEYCODE_SETTINGS), @@ -758,221 +1113,12 @@ class KeyGestureControllerTests { } @Test - @EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT) - fun testTriggerBugReport() { - Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1") - val keyGestureController = KeyGestureController(context, testLooper.looper) - testKeyGestureInternal( - keyGestureController, - TestData( - "META + CTRL + DEL -> Trigger Bug Report", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_CTRL_LEFT, - KeyEvent.KEYCODE_DEL - ), - KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT, - intArrayOf(KeyEvent.KEYCODE_DEL), - KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ) - ) - } - - @Test - @DisableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT) - fun testTriggerBugReport_flagDisabled() { - Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1") - val keyGestureController = KeyGestureController(context, testLooper.looper) - testKeyGestureInternal( - keyGestureController, - TestData( - "META + CTRL + DEL -> Not Trigger Bug Report (Fallback to BACK)", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_CTRL_LEFT, - KeyEvent.KEYCODE_DEL - ), - KeyGestureEvent.KEY_GESTURE_TYPE_BACK, - intArrayOf(KeyEvent.KEYCODE_DEL), - KeyEvent.META_META_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ) - ) - } - - @Test - @EnableFlags( - com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL, - com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG, - com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG, - com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG, - com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS - ) - fun testKeyboardAccessibilityToggleShortcutPress() { - val keyGestureController = KeyGestureController(context, testLooper.looper) - testKeyGestureInternal( - keyGestureController, - TestData( - "Meta + Alt + 3 -> Toggle Bounce Keys", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_3 - ), - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS, - intArrayOf(KeyEvent.KEYCODE_3), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE))) - testKeyGestureInternal( - keyGestureController, - TestData( - "Meta + Alt + 4 -> Toggle Mouse Keys", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_4 - ), - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS, - intArrayOf(KeyEvent.KEYCODE_4), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE))) - testKeyGestureInternal( - keyGestureController, - TestData( - "Meta + Alt + 5 -> Toggle Sticky Keys", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_5 - ), - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS, - intArrayOf(KeyEvent.KEYCODE_5), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE))) - testKeyGestureInternal( - keyGestureController, - TestData( - "Meta + Alt + 6 -> Toggle Slow Keys", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_6 - ), - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS, - intArrayOf(KeyEvent.KEYCODE_6), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE))) - } - - @Test - @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT) - fun testMoveToNextDisplay() { - val keyGestureController = KeyGestureController(context, testLooper.looper) - testKeyGestureInternal( - keyGestureController, - TestData( - "META + CTRL + D -> Move a task to next display", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_CTRL_LEFT, - KeyEvent.KEYCODE_D - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY, - intArrayOf(KeyEvent.KEYCODE_D), - KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ) - ) - } - - @Test - @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS) - fun testSnapLeftFreeformTask() { - val keyGestureController = KeyGestureController(context, testLooper.looper) - testKeyGestureInternal( - keyGestureController, - TestData( - "ALT + [ -> Resizes a task to fit the left half of the screen", - intArrayOf( - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_LEFT_BRACKET - ), - KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW, - intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET), - KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ) - ) - } - - @Test - @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS) - fun testSnapRightFreeformTask() { - val keyGestureController = KeyGestureController(context, testLooper.looper) - testKeyGestureInternal( - keyGestureController, - TestData( - "ALT + ] -> Resizes a task to fit the right half of the screen", - intArrayOf( - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_RIGHT_BRACKET - ), - KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW, - intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET), - KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ) - ) - } - - @Test - @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS) - fun testMaximizeFreeformTask() { - val keyGestureController = KeyGestureController(context, testLooper.looper) - testKeyGestureInternal( - keyGestureController, - TestData( - "ALT + '=' -> Maximizes a task to fit the screen", - intArrayOf( - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_EQUALS - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW, - intArrayOf(KeyEvent.KEYCODE_EQUALS), - KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ) - ) - } - - @Test - @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS) - fun testRestoreFreeformTask() { - val keyGestureController = KeyGestureController(context, testLooper.looper) - testKeyGestureInternal( - keyGestureController, - TestData( - "ALT + '-' -> Restores a task size to its previous bounds", - intArrayOf( - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_MINUS - ), - KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE, - intArrayOf(KeyEvent.KEYCODE_MINUS), - KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ) - ) - } - - @Test fun testCapsLockPressNotified() { - val keyGestureController = KeyGestureController(context, testLooper.looper) + setupKeyGestureController() val listener = KeyGestureEventListener() keyGestureController.registerKeyGestureEventListener(listener, 0) - sendKeys(keyGestureController, intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK)) + sendKeys(intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK)) testLooper.dispatchAll() assertEquals( "Listener should get callbacks on key gesture event completed", @@ -987,7 +1133,7 @@ class KeyGestureControllerTests { } @Keep - private fun keyGestureEventHandlerTestArguments_forKeyCombinations(): Array<TestData> { + private fun systemGesturesTestArguments_forKeyCombinations(): Array<TestData> { return arrayOf( TestData( "VOLUME_DOWN + POWER -> Screenshot Chord", @@ -1048,14 +1194,14 @@ class KeyGestureControllerTests { } @Test - @Parameters(method = "keyGestureEventHandlerTestArguments_forKeyCombinations") + @Parameters(method = "systemGesturesTestArguments_forKeyCombinations") @EnableFlags( com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER, com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_PRESS_GESTURES ) fun testKeyCombinationGestures(test: TestData) { - val keyGestureController = KeyGestureController(context, testLooper.looper) - testKeyGestureInternal(keyGestureController, test) + setupKeyGestureController() + testKeyGestureInternal(test) } @Keep @@ -1096,7 +1242,7 @@ class KeyGestureControllerTests { @Test @Parameters(method = "customInputGesturesTestArguments") fun testCustomKeyGestures(test: TestData) { - val keyGestureController = KeyGestureController(context, testLooper.looper) + setupKeyGestureController() val builder = InputGestureData.Builder() .setKeyGestureType(test.expectedKeyGestureType) .setTrigger( @@ -1104,17 +1250,17 @@ class KeyGestureControllerTests { test.expectedKeys[0], test.expectedModifierState ) - ); + ) if (test.expectedAppLaunchData != null) { builder.setAppLaunchData(test.expectedAppLaunchData) } - val inputGestureData = builder.build(); + val inputGestureData = builder.build() keyGestureController.addCustomInputGesture(0, inputGestureData.aidlData) - testKeyGestureInternal(keyGestureController, test) + testKeyGestureInternal(test) } - private fun testKeyGestureInternal(keyGestureController: KeyGestureController, test: TestData) { + private fun testKeyGestureInternal(test: TestData) { var handleEvents = mutableListOf<KeyGestureEvent>() val handler = KeyGestureHandler { event, _ -> handleEvents.add(KeyGestureEvent(event)) @@ -1123,7 +1269,7 @@ class KeyGestureControllerTests { keyGestureController.registerKeyGestureHandler(handler, 0) handleEvents.clear() - sendKeys(keyGestureController, test.keys) + sendKeys(test.keys) assertEquals( "Test: $test doesn't produce correct number of key gesture events", @@ -1162,11 +1308,7 @@ class KeyGestureControllerTests { keyGestureController.unregisterKeyGestureHandler(handler, 0) } - private fun testKeyGestureNotProduced( - keyGestureController: KeyGestureController, - testName: String, - testKeys: IntArray - ) { + private fun testKeyGestureNotProduced(testName: String, testKeys: IntArray) { var handleEvents = mutableListOf<KeyGestureEvent>() val handler = KeyGestureHandler { event, _ -> handleEvents.add(KeyGestureEvent(event)) @@ -1175,15 +1317,11 @@ class KeyGestureControllerTests { keyGestureController.registerKeyGestureHandler(handler, 0) handleEvents.clear() - sendKeys(keyGestureController, testKeys) + sendKeys(testKeys) assertEquals("Test: $testName should not produce Key gesture", 0, handleEvents.size) } - private fun sendKeys( - keyGestureController: KeyGestureController, - testKeys: IntArray, - assertNotSentToApps: Boolean = false - ) { + private fun sendKeys(testKeys: IntArray, assertNotSentToApps: Boolean = false) { var metaState = 0 val now = SystemClock.uptimeMillis() for (key in testKeys) { @@ -1192,7 +1330,7 @@ class KeyGestureControllerTests { DEVICE_ID, 0 /*scancode*/, 0 /*flags*/, InputDevice.SOURCE_KEYBOARD ) - interceptKey(keyGestureController, downEvent, assertNotSentToApps) + interceptKey(downEvent, assertNotSentToApps) metaState = metaState or MODIFIER.getOrDefault(key, 0) downEvent.recycle() @@ -1205,7 +1343,7 @@ class KeyGestureControllerTests { DEVICE_ID, 0 /*scancode*/, 0 /*flags*/, InputDevice.SOURCE_KEYBOARD ) - interceptKey(keyGestureController, upEvent, assertNotSentToApps) + interceptKey(upEvent, assertNotSentToApps) metaState = metaState and MODIFIER.getOrDefault(key, 0).inv() upEvent.recycle() @@ -1213,11 +1351,7 @@ class KeyGestureControllerTests { } } - private fun interceptKey( - keyGestureController: KeyGestureController, - event: KeyEvent, - assertNotSentToApps: Boolean - ) { + private fun interceptKey(event: KeyEvent, assertNotSentToApps: Boolean) { keyGestureController.interceptKeyBeforeQueueing(event, FLAG_INTERACTIVE) testLooper.dispatchAll() |