diff options
78 files changed, 1618 insertions, 939 deletions
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/core/api/current.txt b/core/api/current.txt index aa58fe42fa26..1a53437b3364 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8024,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"; @@ -9246,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 @@ -9255,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 @@ -9688,10 +9691,8 @@ package android.app.wallpaper { method @Nullable public String getId(); method @Nullable public android.net.Uri getThumbnail(); method @Nullable public CharSequence getTitle(); - method @NonNull public static android.app.wallpaper.WallpaperDescription readFromStream(@NonNull java.io.InputStream) throws java.io.IOException; method @NonNull public android.app.wallpaper.WallpaperDescription.Builder toBuilder(); method public void writeToParcel(@NonNull android.os.Parcel, int); - method public void writeToStream(@NonNull java.io.OutputStream) throws java.io.IOException; field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpaper.WallpaperDescription> CREATOR; } @@ -16419,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 @@ -18709,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 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/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java index c3d6340be41f..8ffda7242b37 100644 --- a/core/java/android/app/wallpaper/WallpaperDescription.java +++ b/core/java/android/app/wallpaper/WallpaperDescription.java @@ -18,8 +18,6 @@ package android.app.wallpaper; import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING; -import static java.nio.charset.StandardCharsets.UTF_8; - import android.annotation.FlaggedApi; import android.app.WallpaperInfo; import android.content.ComponentName; @@ -31,7 +29,6 @@ import android.text.Html; import android.text.Spanned; import android.text.SpannedString; import android.util.Log; -import android.util.Xml; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -43,8 +40,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -154,46 +149,6 @@ public final class WallpaperDescription implements Parcelable { return Objects.hash(mComponent, mId); } - ////// Stream read/write - - /** - * Writes the content of the {@link WallpaperDescription} to a {@link OutputStream}. - * - * <p>The content can be read by {@link #readFromStream}. This method is intended for use by - * trusted apps only, and the format is not guaranteed to be stable.</p> - */ - public void writeToStream(@NonNull OutputStream outputStream) throws IOException { - TypedXmlSerializer serializer = Xml.newFastSerializer(); - serializer.setOutput(outputStream, UTF_8.name()); - serializer.startTag(null, "description"); - try { - saveToXml(serializer); - } catch (XmlPullParserException e) { - throw new IOException(e); - } - serializer.endTag(null, "description"); - serializer.flush(); - } - - /** - * Reads a {@link PersistableBundle} from an {@link InputStream}. - * - * <p>The stream must be generated by {@link #writeToStream}. This method is intended for use by - * trusted apps only, and the format is not guaranteed to be stable.</p> - */ - @NonNull - public static WallpaperDescription readFromStream(@NonNull InputStream inputStream) - throws IOException { - try { - TypedXmlPullParser parser = Xml.newFastPullParser(); - parser.setInput(inputStream, UTF_8.name()); - parser.next(); - return WallpaperDescription.restoreFromXml(parser); - } catch (XmlPullParserException e) { - throw new IOException(e); - } - } - ////// XML storage /** @hide */ diff --git a/core/java/android/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/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index dce4d11aaef3..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" @@ -340,3 +358,12 @@ flag { 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/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/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/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/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt index fc3dc1465dff..f93b35e868f6 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt @@ -20,7 +20,7 @@ import android.os.Looper import android.util.ArrayMap import androidx.dynamicanimation.animation.FloatPropertyCompat import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils.prepareForTest -import java.util.* +import java.util.ArrayDeque import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.collections.ArrayList @@ -74,14 +74,17 @@ object PhysicsAnimatorTestUtils { @JvmStatic fun tearDown() { - val latch = CountDownLatch(1) - animationThreadHandler.post { + if (Looper.myLooper() == animationThreadHandler.looper) { animatorTestHelpers.keys.forEach { it.cancel() } - latch.countDown() + } else { + val latch = CountDownLatch(1) + animationThreadHandler.post { + animatorTestHelpers.keys.forEach { it.cancel() } + latch.countDown() + } + latch.await(5, TimeUnit.SECONDS) } - latch.await() - animatorTestHelpers.clear() animators.clear() allAnimatedObjects.clear() @@ -348,8 +351,9 @@ object PhysicsAnimatorTestUtils { * Returns all of the values that have ever been reported to update listeners, per property. */ @Suppress("UNCHECKED_CAST") - fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>): - UpdateFramesPerProperty<T> { + fun <T : Any> getAnimationUpdateFrames( + animator: PhysicsAnimator<T> + ): UpdateFramesPerProperty<T> { return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T> } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt index 7086691e7431..bd129a28f049 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt @@ -56,6 +56,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi onLeft = initialLocationOnLeft screenCenterX = screenSizeProvider.invoke().x / 2 dismissZone = getExclusionRect() + listener?.onStart(if (initialLocationOnLeft) LEFT else RIGHT) } /** View has moved to [x] and [y] screen coordinates */ @@ -109,6 +110,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi /** Get width for exclusion rect where dismiss takes over drag */ protected abstract fun getExclusionRectWidth(): Float + /** Get height for exclusion rect where dismiss takes over drag */ protected abstract fun getExclusionRectHeight(): Float @@ -184,6 +186,9 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi /** Receive updates on location changes */ interface LocationChangeListener { + /** Bubble bar dragging has started. Includes the initial location of the bar */ + fun onStart(location: BubbleBarLocation) {} + /** * Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in * progress. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 402818c80b01..999ce17905ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -124,18 +124,7 @@ public class BubbleBarLayerView extends FrameLayout mBubbleExpandedViewPinController = new BubbleExpandedViewPinController( context, this, mPositioner); - mBubbleExpandedViewPinController.setListener( - new BaseBubblePinController.LocationChangeListener() { - @Override - public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) { - mBubbleController.animateBubbleBarLocation(bubbleBarLocation); - } - - @Override - public void onRelease(@NonNull BubbleBarLocation location) { - mBubbleController.setBubbleBarLocation(location); - } - }); + mBubbleExpandedViewPinController.setListener(new LocationChangeListener()); setOnClickListener(view -> hideModalOrCollapse()); } @@ -238,11 +227,7 @@ public class BubbleBarLayerView extends FrameLayout DragListener dragListener = inDismiss -> { if (inDismiss && mExpandedBubble != null) { mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE); - if (mExpandedBubble instanceof Bubble) { - // Only a bubble can be dragged to dismiss - mBubbleLogger.log((Bubble) mExpandedBubble, - BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW); - } + logBubbleEvent(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW); } }; mDragController = new BubbleBarExpandedViewDragController( @@ -423,10 +408,47 @@ public class BubbleBarLayerView extends FrameLayout } } + /** + * Log the event only if {@link #mExpandedBubble} is a {@link Bubble}. + * <p> + * Skips logging if it is {@link BubbleOverflow}. + */ + private void logBubbleEvent(BubbleLogger.Event event) { + if (mExpandedBubble != null && mExpandedBubble instanceof Bubble bubble) { + mBubbleLogger.log(bubble, event); + } + } + @Nullable @VisibleForTesting public BubbleBarExpandedViewDragController getDragController() { return mDragController; } + private class LocationChangeListener implements + BaseBubblePinController.LocationChangeListener { + + private BubbleBarLocation mInitialLocation; + + @Override + public void onStart(@NonNull BubbleBarLocation location) { + mInitialLocation = location; + } + + @Override + public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) { + mBubbleController.animateBubbleBarLocation(bubbleBarLocation); + } + + @Override + public void onRelease(@NonNull BubbleBarLocation location) { + mBubbleController.setBubbleBarLocation(location); + if (location != mInitialLocation) { + BubbleLogger.Event event = location.isOnLeft(isLayoutRtl()) + ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW + : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW; + logBubbleEvent(event); + } + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt index b83b5f341dda..8ef20d1d6b93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -44,7 +44,8 @@ object PipUtils { private const val TAG = "PipUtils" // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. - private const val EPSILON = 1e-7 + // TODO b/377530560: Restore epsilon once a long term fix is merged for non-config-at-end issue. + private const val EPSILON = 0.05f /** * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java index 24077a35d41c..026482004d51 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java @@ -24,7 +24,6 @@ import android.view.Choreographer; import android.view.SurfaceControl; import com.android.wm.shell.R; -import com.android.wm.shell.transition.Transitions; /** * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition. @@ -180,8 +179,7 @@ public class PipSurfaceTransactionHelper { // destination are different. final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH; final Rect crop = mTmpDestinationRect; - crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH - : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH); + crop.set(0, 0, destW, destH); // Inverse scale for crop to fit in screen coordinates. crop.scale(1 / scale); crop.offset(insets.left, insets.top); @@ -200,8 +198,8 @@ public class PipSurfaceTransactionHelper { } } mTmpTransform.setScale(scale, scale); - mTmpTransform.postRotate(degrees); mTmpTransform.postTranslate(positionX, positionY); + mTmpTransform.postRotate(degrees); tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setCrop(leash, crop); return this; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java index 3f9b0c30e314..fb1aba399585 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java @@ -16,6 +16,8 @@ package com.android.wm.shell.pip2.animation; +import static android.view.Surface.ROTATION_90; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.RectEvaluator; @@ -73,6 +75,7 @@ public class PipExpandAnimator extends ValueAnimator { mAnimationStartCallback.run(); } if (mStartTransaction != null) { + onExpandAnimationUpdate(mStartTransaction, 0f); mStartTransaction.apply(); } } @@ -81,13 +84,7 @@ public class PipExpandAnimator extends ValueAnimator { public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); if (mFinishTransaction != null) { - // finishTransaction might override some state (eg. corner radii) so we want to - // manually set the state to the end of the animation - mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash, - mSourceRectHint, mBaseBounds, mAnimatedRect, getInsets(1f), - false /* isInPipDirection */, 1f) - .round(mFinishTransaction, mLeash, false /* applyCornerRadius */) - .shadow(mFinishTransaction, mLeash, false /* applyCornerRadius */); + onExpandAnimationUpdate(mFinishTransaction, 1f); } if (mAnimationEndCallback != null) { mAnimationEndCallback.run(); @@ -102,14 +99,7 @@ public class PipExpandAnimator extends ValueAnimator { final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); final float fraction = getAnimatedFraction(); - - // TODO (b/350801661): implement fixed rotation - Rect insets = getInsets(fraction); - mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, - mBaseBounds, mAnimatedRect, - insets, false /* isInPipDirection */, fraction) - .round(tx, mLeash, false /* applyCornerRadius */) - .shadow(tx, mLeash, false /* applyCornerRadius */); + onExpandAnimationUpdate(tx, fraction); tx.apply(); } }; @@ -167,6 +157,32 @@ public class PipExpandAnimator extends ValueAnimator { mAnimationEndCallback = runnable; } + private void onExpandAnimationUpdate(SurfaceControl.Transaction tx, float fraction) { + Rect insets = getInsets(fraction); + if (mRotation == Surface.ROTATION_0) { + mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, mBaseBounds, + mAnimatedRect, insets, false /* isInPipDirection */, fraction); + } else { + // Fixed rotation case. + Rect start = mStartBounds; + Rect end = mEndBounds; + float degrees, x, y; + x = fraction * (end.left - start.left) + start.left; + y = fraction * (end.top - start.top) + start.top; + + if (mRotation == ROTATION_90) { + degrees = 90 * fraction; + } else { + degrees = -90 * fraction; + } + mPipSurfaceTransactionHelper.rotateAndScaleWithCrop(tx, mLeash, mBaseBounds, + mAnimatedRect, insets, degrees, x, y, + true /* isExpanding */, mRotation == ROTATION_90); + } + mPipSurfaceTransactionHelper.round(tx, mLeash, false /* applyCornerRadius */) + .shadow(tx, mLeash, false /* applyShadowRadius */); + } + private Rect getInsets(float fraction) { final Rect startInsets = mSourceRectHintInsets; final Rect endInsets = mZeroInsets; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java index 012dabbbb9f8..4558a9f141c8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java @@ -30,6 +30,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; +import com.android.wm.shell.shared.animation.Interpolators; /** * Animator that handles any resize related animation for PIP. @@ -128,6 +129,7 @@ public class PipResizeAnimator extends ValueAnimator { mRectEvaluator = new RectEvaluator(mAnimatedRect); setObjectValues(startBounds, endBounds); + setInterpolator(Interpolators.FAST_OUT_SLOW_IN); addListener(mAnimatorListener); addUpdateListener(mAnimatorUpdateListener); setEvaluator(mRectEvaluator); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/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/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 6bf92f69cfb6..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,7 +347,7 @@ public class PipTransition extends PipTransitionController implements : startRotation - endRotation; if (delta != ROTATION_0) { mPipTransitionState.setInFixedRotation(true); - handleBoundsTypeFixedRotation(pipChange, pipActivityChange, endRotation); + handleBoundsEnterFixedRotation(pipChange, pipActivityChange, endRotation); } prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange, @@ -414,15 +412,15 @@ public class PipTransition extends PipTransitionController implements } final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); - int startRotation = pipChange.getStartRotation(); - int endRotation = fixedRotationChange != null + final int startRotation = pipChange.getStartRotation(); + final int endRotation = fixedRotationChange != null ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 : startRotation - endRotation; if (delta != ROTATION_0) { mPipTransitionState.setInFixedRotation(true); - handleBoundsTypeFixedRotation(pipChange, pipActivityChange, + handleBoundsEnterFixedRotation(pipChange, pipActivityChange, fixedRotationChange.getEndFixedRotation()); } @@ -459,7 +457,7 @@ public class PipTransition extends PipTransitionController implements animator.start(); } - private void handleBoundsTypeFixedRotation(TransitionInfo.Change pipTaskChange, + private void handleBoundsEnterFixedRotation(TransitionInfo.Change pipTaskChange, TransitionInfo.Change pipActivityChange, int endRotation) { final Rect endBounds = pipTaskChange.getEndAbsBounds(); final Rect endActivityBounds = pipActivityChange.getEndAbsBounds(); @@ -492,6 +490,26 @@ public class PipTransition extends PipTransitionController implements endBounds.top + activityEndOffset.y); } + private void handleExpandFixedRotation(TransitionInfo.Change pipTaskChange, int endRotation) { + final Rect endBounds = pipTaskChange.getEndAbsBounds(); + final int width = endBounds.width(); + final int height = endBounds.height(); + final int left = endBounds.left; + final int top = endBounds.top; + int newTop, newLeft; + + if (endRotation == Surface.ROTATION_90) { + newLeft = top; + newTop = -(left + width); + } else { + newLeft = -(height + top); + newTop = left; + } + // Modify the endBounds, rotating and placing them potentially off-screen, so that + // as we translate and rotate around the origin, we place them right into the target. + endBounds.set(newLeft, newTop, newLeft + height, newTop + width); + } + private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -544,33 +562,51 @@ public class PipTransition extends PipTransitionController implements } } - // for multi activity, we need to manually set the leash layer - if (pipChange.getTaskInfo() == null) { - TransitionInfo.Change parent = getChangeByToken(info, pipChange.getParent()); - if (parent != null) { - startTransaction.setLayer(parent.getLeash(), Integer.MAX_VALUE - 1); - } + // The parent change if we were in a multi-activity PiP; null if single activity PiP. + final TransitionInfo.Change parentBeforePip = pipChange.getTaskInfo() == null + ? getChangeByToken(info, pipChange.getParent()) : null; + if (parentBeforePip != null) { + // For multi activity, we need to manually set the leash layer + startTransaction.setLayer(parentBeforePip.getLeash(), Integer.MAX_VALUE - 1); } - Rect startBounds = pipChange.getStartAbsBounds(); - Rect endBounds = pipChange.getEndAbsBounds(); - SurfaceControl pipLeash = pipChange.getLeash(); - Preconditions.checkNotNull(pipLeash, "Leash is null for exit transition."); + final Rect startBounds = pipChange.getStartAbsBounds(); + final Rect endBounds = pipChange.getEndAbsBounds(); + final SurfaceControl pipLeash = getLeash(pipChange); - Rect sourceRectHint = null; - if (pipChange.getTaskInfo() != null - && pipChange.getTaskInfo().pictureInPictureParams != null) { + PictureInPictureParams params = null; + if (pipChange.getTaskInfo() != null) { // single activity - sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint(); - } else if (mPipTaskListener.getPictureInPictureParams().hasSourceBoundsHint()) { + params = pipChange.getTaskInfo().pictureInPictureParams; + } else if (parentBeforePip != null && parentBeforePip.getTaskInfo() != null) { // multi activity - sourceRectHint = mPipTaskListener.getPictureInPictureParams().getSourceRectHint(); + params = parentBeforePip.getTaskInfo().pictureInPictureParams; + } + final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds, + startBounds); + + final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); + final int startRotation = pipChange.getStartRotation(); + final int endRotation = fixedRotationChange != null + ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; + final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 + : endRotation - startRotation; + + if (delta != ROTATION_0) { + handleExpandFixedRotation(pipChange, endRotation); } PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash, startTransaction, finishTransaction, endBounds, startBounds, endBounds, - sourceRectHint, Surface.ROTATION_0); - animator.setAnimationEndCallback(this::finishTransition); + sourceRectHint, delta); + animator.setAnimationEndCallback(() -> { + if (parentBeforePip != null) { + // TODO b/377362511: Animate local leash instead to also handle letterbox case. + // For multi-activity, set the crop to be null + finishTransaction.setCrop(pipLeash, null); + } + finishTransition(); + }); animator.start(); return true; } @@ -717,6 +753,13 @@ public class PipTransition extends PipTransitionController implements } } + @NonNull + private SurfaceControl getLeash(TransitionInfo.Change change) { + SurfaceControl leash = change.getLeash(); + Preconditions.checkNotNull(leash, "Leash is null for change=" + change); + return leash; + } + // // Miscellaneous callbacks and listeners // 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/hwui/Android.bp b/libs/hwui/Android.bp index b71abdc011c1..fcb7efc35c94 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -580,6 +580,7 @@ cc_defaults { "utils/Color.cpp", "utils/LinearAllocator.cpp", "utils/StringUtils.cpp", + "utils/StatsUtils.cpp", "utils/TypefaceUtils.cpp", "utils/VectorDrawableUtils.cpp", "AnimationContext.cpp", diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index e074a27db38f..a9a5db8181ba 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -27,8 +27,8 @@ #include <SkColorSpace.h> #include <SkColorType.h> #include <SkEncodedOrigin.h> -#include <SkImageInfo.h> #include <SkGainmapInfo.h> +#include <SkImageInfo.h> #include <SkMatrix.h> #include <SkPaint.h> #include <SkPngChunkReader.h> @@ -43,6 +43,8 @@ #include <memory> +#include "modules/skcms/src/skcms_public.h" + using namespace android; sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const { diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp index 49a7f73fb3a3..8b43f1db84af 100644 --- a/libs/hwui/jni/BitmapFactory.cpp +++ b/libs/hwui/jni/BitmapFactory.cpp @@ -10,6 +10,7 @@ #include <stdint.h> #include <stdio.h> #include <sys/stat.h> +#include <utils/StatsUtils.h> #include <memory> @@ -630,6 +631,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, } bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied); outputBitmap.notifyPixelsChanged(); + uirenderer::logBitmapDecode(*reuseBitmap); // If a java bitmap was passed in for reuse, pass it back return javaBitmap; } @@ -650,6 +652,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, } } + uirenderer::logBitmapDecode(*hardwareBitmap); return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); } @@ -659,6 +662,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, heapBitmap->setGainmap(std::move(gainmap)); } + uirenderer::logBitmapDecode(*heapBitmap); // now create the java bitmap return bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp index f7e8e073a272..5ffd5b9016d8 100644 --- a/libs/hwui/jni/BitmapRegionDecoder.cpp +++ b/libs/hwui/jni/BitmapRegionDecoder.cpp @@ -19,6 +19,7 @@ #include <HardwareBitmapUploader.h> #include <androidfw/Asset.h> #include <sys/stat.h> +#include <utils/StatsUtils.h> #include <memory> @@ -376,6 +377,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in recycledBitmap->setGainmap(std::move(gainmap)); } bitmap::reinitBitmap(env, javaBitmap, recycledBitmap->info(), !requireUnpremul); + uirenderer::logBitmapDecode(*recycledBitmap); return javaBitmap; } @@ -392,12 +394,14 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in hardwareBitmap->setGainmap(std::move(gm)); } } + uirenderer::logBitmapDecode(*hardwareBitmap); return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags); } Bitmap* heapBitmap = heapAlloc.getStorageObjAndReset(); if (hasGainmap && heapBitmap != nullptr) { heapBitmap->setGainmap(std::move(gainmap)); } + uirenderer::logBitmapDecode(*heapBitmap); return android::bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags); } diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp index aebc4db37898..90fd3d87cba7 100644 --- a/libs/hwui/jni/ImageDecoder.cpp +++ b/libs/hwui/jni/ImageDecoder.cpp @@ -37,6 +37,7 @@ #include <hwui/Bitmap.h> #include <hwui/ImageDecoder.h> #include <sys/stat.h> +#include <utils/StatsUtils.h> #include "Bitmap.h" #include "BitmapFactory.h" @@ -485,6 +486,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong hwBitmap->setGainmap(std::move(gm)); } } + uirenderer::logBitmapDecode(*hwBitmap); return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets); } @@ -498,6 +500,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativeBitmap->setImmutable(); } + + uirenderer::logBitmapDecode(*nativeBitmap); return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets); } diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt index 2414299321a9..b5591941453d 100644 --- a/libs/hwui/libhwui.map.txt +++ b/libs/hwui/libhwui.map.txt @@ -67,6 +67,7 @@ LIBHWUI_PLATFORM { SkFILEStream::SkFILEStream*; SkImageInfo::*; SkMemoryStream::SkMemoryStream*; + android::uirenderer::logBitmapDecode*; }; local: *; diff --git a/libs/hwui/utils/StatsUtils.cpp b/libs/hwui/utils/StatsUtils.cpp new file mode 100644 index 000000000000..5c4027e1a846 --- /dev/null +++ b/libs/hwui/utils/StatsUtils.cpp @@ -0,0 +1,102 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef __ANDROID__ +#include <dlfcn.h> +#include <log/log.h> +#include <statslog_hwui.h> +#include <statssocket_lazy.h> +#include <utils/Errors.h> + +#include <mutex> +#endif + +#include <unistd.h> + +#include "StatsUtils.h" + +namespace android { +namespace uirenderer { + +#ifdef __ANDROID__ + +namespace { + +int32_t toStatsColorSpaceTransfer(skcms_TFType transferType) { + switch (transferType) { + case skcms_TFType_sRGBish: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_SRGBISH; + case skcms_TFType_PQish: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_PQISH; + case skcms_TFType_HLGish: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_HLGISH; + default: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_UNKNOWN; + } +} + +int32_t toStatsBitmapFormat(SkColorType type) { + switch (type) { + case kAlpha_8_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_A_8; + case kRGB_565_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGB_565; + case kN32_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_ARGB_8888; + case kRGBA_F16_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_F16; + case kRGBA_1010102_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_1010102; + default: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_UNKNOWN; + } +} + +} // namespace + +#endif + +void logBitmapDecode(const SkImageInfo& info, bool hasGainmap) { +#ifdef __ANDROID__ + + if (!statssocket::lazy::IsAvailable()) { + std::once_flag once; + std::call_once(once, []() { ALOGD("libstatssocket not available, dropping stats"); }); + return; + } + + skcms_TFType tfnType = skcms_TFType_Invalid; + + if (info.colorSpace()) { + skcms_TransferFunction tfn; + info.colorSpace()->transferFn(&tfn); + tfnType = skcms_TransferFunction_getType(&tfn); + } + + auto status = + stats::stats_write(uirenderer::stats::IMAGE_DECODED, static_cast<int32_t>(getuid()), + uirenderer::toStatsColorSpaceTransfer(tfnType), hasGainmap, + uirenderer::toStatsBitmapFormat(info.colorType())); + ALOGW_IF(status != OK, "Image decoding logging dropped!"); +#endif +} + +void logBitmapDecode(const Bitmap& bitmap) { + logBitmapDecode(bitmap.info(), bitmap.hasGainmap()); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/StatsUtils.h b/libs/hwui/utils/StatsUtils.h new file mode 100644 index 000000000000..0c247014a8eb --- /dev/null +++ b/libs/hwui/utils/StatsUtils.h @@ -0,0 +1,33 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <SkBitmap.h> +#include <SkColorSpace.h> +#include <SkColorType.h> +#include <cutils/compiler.h> +#include <hwui/Bitmap.h> + +namespace android { +namespace uirenderer { + +ANDROID_API void logBitmapDecode(const SkImageInfo& info, bool hasGainmap); + +ANDROID_API void logBitmapDecode(const Bitmap& bitmap); + +} // namespace uirenderer +} // namespace android diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java index 55c8ed5debf8..530d48d3e60b 100644 --- a/media/java/android/media/ImageReader.java +++ b/media/java/android/media/ImageReader.java @@ -40,6 +40,8 @@ import android.os.ParcelFileDescriptor; import android.os.Trace; import android.view.Surface; +import com.android.internal.camera.flags.Flags; + import dalvik.system.VMRuntime; import java.io.IOException; @@ -1208,6 +1210,11 @@ public class ImageReader implements AutoCloseable { default: width = nativeGetWidth(); } + if (Flags.cameraHeifGainmap()) { + if (getFormat() == ImageFormat.HEIC_ULTRAHDR){ + width = ImageReader.this.getWidth(); + } + } return width; } @@ -1227,6 +1234,11 @@ public class ImageReader implements AutoCloseable { default: height = nativeGetHeight(); } + if (Flags.cameraHeifGainmap()) { + if (getFormat() == ImageFormat.HEIC_ULTRAHDR){ + height = ImageReader.this.getHeight(); + } + } return height; } diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java index f4caad727407..c7678067f0b5 100644 --- a/media/java/android/media/ImageUtils.java +++ b/media/java/android/media/ImageUtils.java @@ -23,6 +23,8 @@ import android.media.Image.Plane; import android.util.Log; import android.util.Size; +import com.android.internal.camera.flags.Flags; + import libcore.io.Memory; import java.nio.ByteBuffer; @@ -44,6 +46,11 @@ class ImageUtils { * are used. */ public static int getNumPlanesForFormat(int format) { + if (Flags.cameraHeifGainmap()) { + if (format == ImageFormat.HEIC_ULTRAHDR) { + return 1; + } + } switch (format) { case ImageFormat.YV12: case ImageFormat.YUV_420_888: @@ -229,6 +236,11 @@ class ImageUtils { public static int getEstimatedNativeAllocBytes(int width, int height, int format, int numImages) { double estimatedBytePerPixel; + if (Flags.cameraHeifGainmap()) { + if (format == ImageFormat.HEIC_ULTRAHDR) { + estimatedBytePerPixel = 0.3; + } + } switch (format) { // 10x compression from RGB_888 case ImageFormat.JPEG: @@ -283,6 +295,11 @@ class ImageUtils { } private static Size getEffectivePlaneSizeForImage(Image image, int planeIdx) { + if (Flags.cameraHeifGainmap()) { + if (image.getFormat() == ImageFormat.HEIC_ULTRAHDR){ + return new Size(image.getWidth(), image.getHeight()); + } + } switch (image.getFormat()) { case ImageFormat.YCBCR_P010: case ImageFormat.YV12: diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp index 0fb3049f63d8..23dd9b7aee37 100644 --- a/native/graphics/jni/Android.bp +++ b/native/graphics/jni/Android.bp @@ -48,7 +48,9 @@ cc_library_shared { "libhwui_internal_headers", ], - static_libs: ["libarect"], + static_libs: [ + "libarect", + ], host_supported: true, target: { @@ -60,6 +62,11 @@ cc_library_shared { shared_libs: [ "libandroid", ], + static_libs: [ + "libstatslog_hwui", + "libstatspull_lazy", + "libstatssocket_lazy", + ], version_script: "libjnigraphics.map.txt", }, host: { diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp index e18b4a9d2420..cb95bcf5d2b1 100644 --- a/native/graphics/jni/imagedecoder.cpp +++ b/native/graphics/jni/imagedecoder.cpp @@ -14,18 +14,9 @@ * limitations under the License. */ -#include "aassetstreamadaptor.h" - -#include <android/asset_manager.h> -#include <android/bitmap.h> -#include <android/data_space.h> -#include <android/imagedecoder.h> #include <MimeType.h> -#include <android/rect.h> -#include <hwui/ImageDecoder.h> -#include <log/log.h> -#include <SkAndroidCodec.h> #include <SkAlphaType.h> +#include <SkAndroidCodec.h> #include <SkCodec.h> #include <SkCodecAnimation.h> #include <SkColorSpace.h> @@ -35,14 +26,24 @@ #include <SkRefCnt.h> #include <SkSize.h> #include <SkStream.h> -#include <utils/Color.h> - +#include <android/asset_manager.h> +#include <android/bitmap.h> +#include <android/data_space.h> +#include <android/imagedecoder.h> +#include <android/rect.h> #include <fcntl.h> -#include <limits> -#include <optional> +#include <hwui/ImageDecoder.h> +#include <log/log.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> +#include <utils/Color.h> +#include <utils/StatsUtils.h> + +#include <limits> +#include <optional> + +#include "aassetstreamadaptor.h" using namespace android; @@ -400,9 +401,7 @@ size_t AImageDecoder_getMinimumStride(AImageDecoder* decoder) { return info.minRowBytes(); } -int AImageDecoder_decodeImage(AImageDecoder* decoder, - void* pixels, size_t stride, - size_t size) { +int AImageDecoder_decodeImage(AImageDecoder* decoder, void* pixels, size_t stride, size_t size) { if (!decoder || !pixels || !stride) { return ANDROID_IMAGE_DECODER_BAD_PARAMETER; } @@ -419,7 +418,13 @@ int AImageDecoder_decodeImage(AImageDecoder* decoder, return ANDROID_IMAGE_DECODER_FINISHED; } - return ResultToErrorCode(imageDecoder->decode(pixels, stride)); + const auto result = ResultToErrorCode(imageDecoder->decode(pixels, stride)); + + if (result == ANDROID_IMAGE_DECODER_SUCCESS) { + uirenderer::logBitmapDecode(imageDecoder->getOutputInfo(), false); + } + + return result; } void AImageDecoder_delete(AImageDecoder* decoder) { diff --git a/packages/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/docs/demo_mode.md b/packages/SystemUI/docs/demo_mode.md index ade5171d6415..c18d1f143b85 100644 --- a/packages/SystemUI/docs/demo_mode.md +++ b/packages/SystemUI/docs/demo_mode.md @@ -35,6 +35,7 @@ Commands are sent as string extras with key ```command``` (required). Possible v | | ```fully``` | | Sets MCS state to fully connected (```true```, ```false```) | | ```wifi``` | | ```show``` to show icon, any other value to hide | | | ```level``` | Sets wifi level (null or 0-4) +| | | ```hotspot``` | Sets the wifi to be from an Instant Hotspot. Values: ```none```, ```unknown```, ```phone```, ```tablet```, ```laptop```, ```watch```, ```auto```. (See `DemoModeWifiDataSource.kt`.) | | ```mobile``` | | ```show``` to show icon, any other value to hide | | | ```datatype``` | Values: ```1x```, ```3g```, ```4g```, ```e```, ```g```, ```h```, ```lte```, ```roam```, any other value to hide | | | ```level``` | Sets mobile signal strength level (null or 0-4) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/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/common/ui/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt index 5994afa948c7..72e0726dedb0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt @@ -55,7 +55,7 @@ class ConfigurationInteractorTest : SysuiTestCase() { testableResources.overrideConfiguration(configuration) configurationRepository = FakeConfigurationRepository() testScope = TestScope() - underTest = ConfigurationInteractorImpl(configurationRepository) + underTest = ConfigurationInteractor(configurationRepository) } @Test @@ -207,7 +207,7 @@ class ConfigurationInteractorTest : SysuiTestCase() { updateDisplay( width = DISPLAY_HEIGHT, height = DISPLAY_WIDTH, - rotation = Surface.ROTATION_90, + rotation = Surface.ROTATION_90 ) runCurrent() @@ -217,7 +217,7 @@ class ConfigurationInteractorTest : SysuiTestCase() { private fun updateDisplay( width: Int = DISPLAY_WIDTH, height: Int = DISPLAY_HEIGHT, - @Surface.Rotation rotation: Int = Surface.ROTATION_0, + @Surface.Rotation rotation: Int = Surface.ROTATION_0 ) { configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height)) configuration.windowConfiguration.displayRotation = rotation 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/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt index a0bef727e548..2735d2f03e6a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt @@ -24,7 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor import com.google.common.truth.Truth.assertThat @@ -59,7 +59,7 @@ class KeyboardDockingIndicationViewModelTest : SysuiTestCase() { val keyboardDockingIndicationInteractor = KeyboardDockingIndicationInteractor(keyboardRepository) - val configurationInteractor = ConfigurationInteractorImpl(configurationRepository) + val configurationInteractor = ConfigurationInteractor(configurationRepository) underTest = KeyboardDockingIndicationViewModel( @@ -67,7 +67,7 @@ class KeyboardDockingIndicationViewModelTest : SysuiTestCase() { context, keyboardDockingIndicationInteractor, configurationInteractor, - testScope.backgroundScope, + testScope.backgroundScope ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt index 8769022f3aa8..22913f12330f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt @@ -26,7 +26,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.domain.interactor.displayStateInteractor import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.display.data.repository.displayStateRepository import com.android.systemui.dump.DumpManager @@ -101,7 +101,7 @@ class QSSceneAdapterImplTest : SysuiTestCase() { private val fakeConfigurationRepository = FakeConfigurationRepository().apply { onConfigurationChange(configuration) } - private val configurationInteractor = ConfigurationInteractorImpl(fakeConfigurationRepository) + private val configurationInteractor = ConfigurationInteractor(fakeConfigurationRepository) private val mockAsyncLayoutInflater = mock<AsyncLayoutInflater>() { @@ -151,7 +151,10 @@ class QSSceneAdapterImplTest : SysuiTestCase() { inOrder.verify(qsImpl!!).onCreate(nullable()) inOrder .verify(qsImpl!!) - .onComponentCreated(eq(qsSceneComponentFactory.components[0]), any()) + .onComponentCreated( + eq(qsSceneComponentFactory.components[0]), + any(), + ) } @Test @@ -419,7 +422,10 @@ class QSSceneAdapterImplTest : SysuiTestCase() { inOrder.verify(newQSImpl).onCreate(nullable()) inOrder .verify(newQSImpl) - .onComponentCreated(qsSceneComponentFactory.components[1], bundleArgCaptor.value) + .onComponentCreated( + qsSceneComponentFactory.components[1], + bundleArgCaptor.value, + ) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index 29b6cbe47ca6..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 @@ -168,7 +168,8 @@ class NotifChipsViewModelTest : SysuiTestCase() { companion object { fun assertIsNotifChip(latest: OngoingActivityChipModel?, expectedIcon: StatusBarIconView) { - assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java) + assertThat(latest) + .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) assertThat((latest as OngoingActivityChipModel.Shown).icon) .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon)) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt index d665b3166986..476252737454 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt @@ -24,7 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.coroutines.collectValues import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractor @@ -75,9 +75,9 @@ open class HideNotificationsInteractorTest : SysuiTestCase() { configurationController, context, testScope.backgroundScope, - mock(), + mock() ) - private val configurationInteractor = ConfigurationInteractorImpl(configurationRepository) + private val configurationInteractor = ConfigurationInteractor(configurationRepository) private val unfoldTransitionRepository = UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider)) @@ -103,7 +103,7 @@ open class HideNotificationsInteractorTest : SysuiTestCase() { unfoldTransitionInteractor, configurationInteractor, animationStatus, - powerInteractor, + powerInteractor ) } @@ -140,7 +140,7 @@ open class HideNotificationsInteractorTest : SysuiTestCase() { updateDisplay( width = INITIAL_DISPLAY_HEIGHT, height = INITIAL_DISPLAY_WIDTH, - rotation = ROTATION_90, + rotation = ROTATION_90 ) runCurrent() @@ -284,7 +284,7 @@ open class HideNotificationsInteractorTest : SysuiTestCase() { private fun updateDisplay( width: Int = INITIAL_DISPLAY_WIDTH, height: Int = INITIAL_DISPLAY_HEIGHT, - @Surface.Rotation rotation: Int = ROTATION_0, + @Surface.Rotation rotation: Int = ROTATION_0 ) { configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height)) configuration.windowConfiguration.displayRotation = rotation diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt index 09be93de9f3e..f1015394d7b1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt @@ -24,7 +24,7 @@ import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.defaultDeviceState import com.android.systemui.deviceStateManager import com.android.systemui.display.data.repository.DeviceStateRepository @@ -107,9 +107,9 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { configurationController, context, testScope.backgroundScope, - mock(), + mock() ) - private val configurationInteractor = ConfigurationInteractorImpl(configurationRepository) + private val configurationInteractor = ConfigurationInteractor(configurationRepository) private val unfoldTransitionProgressProvider = FakeUnfoldTransitionProvider() private val unfoldTransitionRepository = UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider)) @@ -145,7 +145,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { testScope.backgroundScope, displaySwitchLatencyLogger, systemClock, - deviceStateManager, + deviceStateManager ) } @@ -174,7 +174,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { DisplaySwitchLatencyEvent( latencyMs = 250, fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED, - toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN, + toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN ) assertThat(loggedEvent).isEqualTo(expectedLoggedEvent) } @@ -200,7 +200,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { testScope.backgroundScope, displaySwitchLatencyLogger, systemClock, - deviceStateManager, + deviceStateManager ) areAnimationEnabled.emit(true) @@ -226,7 +226,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { DisplaySwitchLatencyEvent( latencyMs = 50, fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED, - toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN, + toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN ) assertThat(loggedEvent).isEqualTo(expectedLoggedEvent) } @@ -259,7 +259,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { DisplaySwitchLatencyEvent( latencyMs = 50, fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED, - toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN, + toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN ) assertThat(loggedEvent).isEqualTo(expectedLoggedEvent) } @@ -289,7 +289,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { DisplaySwitchLatencyEvent( latencyMs = 200, fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN, - toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED, + toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED ) assertThat(loggedEvent).isEqualTo(expectedLoggedEvent) } @@ -310,7 +310,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { lastWakefulnessEvent.emit( WakefulnessModel( internalWakefulnessState = WakefulnessState.ASLEEP, - lastSleepReason = WakeSleepReason.FOLD, + lastSleepReason = WakeSleepReason.FOLD ) ) screenPowerState.emit(ScreenPowerState.SCREEN_OFF) @@ -326,7 +326,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { latencyMs = 200, fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN, toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED, - toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD, + toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD ) assertThat(loggedEvent).isEqualTo(expectedLoggedEvent) } @@ -372,7 +372,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { lastWakefulnessEvent.emit( WakefulnessModel( internalWakefulnessState = WakefulnessState.ASLEEP, - lastSleepReason = WakeSleepReason.FOLD, + lastSleepReason = WakeSleepReason.FOLD ) ) screenPowerState.emit(ScreenPowerState.SCREEN_OFF) @@ -385,7 +385,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { latencyMs = 0, fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN, toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED, - toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__SCREEN_OFF, + toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__SCREEN_OFF ) assertThat(loggedEvent).isEqualTo(expectedLoggedEvent) } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt index 7f50e4a06022..a3735f95cf74 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt @@ -17,9 +17,6 @@ package com.android.systemui.common.ui import android.content.Context -import com.android.systemui.common.ui.data.repository.ConfigurationRepository -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.policy.ConfigurationController @@ -38,24 +35,17 @@ import javax.inject.Qualifier @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class GlobalConfig @Module -interface ConfigurationModule { +interface ConfigurationStateModule { /** * Deprecated: [ConfigurationState] should be injected only with the correct annotation. For * now, without annotation the global config associated state is provided. */ @Binds - @Deprecated("Use the @GlobalConfig annotated one instead of this.") fun provideGlobalConfigurationState( @GlobalConfig configurationState: ConfigurationState ): ConfigurationState - @Binds - @Deprecated("Use the @GlobalConfig annotated one instead of this.") - fun provideDefaultConfigurationState( - @GlobalConfig configurationState: ConfigurationInteractor - ): ConfigurationInteractor - companion object { @SysUISingleton @Provides @@ -67,14 +57,5 @@ interface ConfigurationModule { ): ConfigurationState { return configStateFactory.create(context, configurationController) } - - @SysUISingleton - @Provides - @GlobalConfig - fun provideGlobalConfigurationInteractor( - configurationRepository: ConfigurationRepository - ): ConfigurationInteractor { - return ConfigurationInteractorImpl(configurationRepository) - } } } 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 97a23e1a5010..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 @@ -21,6 +21,8 @@ import android.content.res.Configuration import android.graphics.Rect import android.view.Surface import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -30,52 +32,14 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onStart /** Business logic related to configuration changes. */ -interface ConfigurationInteractor { +// TODO: b/374267505 - Create a @ShadeDisplayWindow annotated version of this. +@SysUISingleton +class ConfigurationInteractor @Inject constructor(private val repository: ConfigurationRepository) { /** * Returns screen size adjusted to rotation, so returned screen size is stable across all * rotations */ - val Configuration.naturalScreenBounds: Rect - - /** Returns the unadjusted screen size. */ - val maxBounds: Flow<Rect> - - /** - * Returns screen size adjusted to rotation, so returned screen sizes are stable across all - * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on - * foldable devices) - */ - val naturalMaxBounds: Flow<Rect> - - /** - * The layout direction. Will be either `View#LAYOUT_DIRECTION_LTR` or - * `View#LAYOUT_DIRECTION_RTL`. - */ - val layoutDirection: Flow<Int> - - /** Emit an event on any config change */ - val onAnyConfigurationChange: Flow<Unit> - - /** Emits the new configuration on any configuration change */ - val configurationValues: Flow<Configuration> - - /** Emits the current resolution scaling factor */ - val scaleForResolution: Flow<Float> - - /** Given [resourceId], emit the dimension pixel size on config change */ - fun dimensionPixelSize(resourceId: Int): Flow<Int> - - /** Emits the dimensional pixel size of the given resource, inverting it for RTL if necessary */ - fun directionalDimensionPixelSize(originLayoutDirection: Int, resourceId: Int): Flow<Int> - - /** Given a set of [resourceId]s, emit Map<ResourceId, DimensionPixelSize> on config change */ - fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>> -} - -class ConfigurationInteractorImpl(private val repository: ConfigurationRepository) : - ConfigurationInteractor { - - override val Configuration.naturalScreenBounds: Rect + private val Configuration.naturalScreenBounds: Rect get() { val rotation = windowConfiguration.displayRotation val maxBounds = windowConfiguration.maxBounds @@ -86,40 +50,53 @@ class ConfigurationInteractorImpl(private val repository: ConfigurationRepositor } } - override val maxBounds: Flow<Rect> = + /** Returns the unadjusted screen size. */ + val maxBounds: Flow<Rect> = repository.configurationValues .map { Rect(it.windowConfiguration.maxBounds) } .distinctUntilChanged() - override val naturalMaxBounds: Flow<Rect> = + /** + * Returns screen size adjusted to rotation, so returned screen sizes are stable across all + * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on + * foldable devices) + */ + val naturalMaxBounds: Flow<Rect> = repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged() - override val layoutDirection: Flow<Int> = + /** + * The layout direction. Will be either `View#LAYOUT_DIRECTION_LTR` or + * `View#LAYOUT_DIRECTION_RTL`. + */ + val layoutDirection: Flow<Int> = repository.configurationValues.map { it.layoutDirection }.distinctUntilChanged() - override fun dimensionPixelSize(resourceId: Int): Flow<Int> { + /** Given [resourceId], emit the dimension pixel size on config change */ + fun dimensionPixelSize(resourceId: Int): Flow<Int> { return onAnyConfigurationChange.mapLatest { repository.getDimensionPixelSize(resourceId) } } - override fun directionalDimensionPixelSize( - originLayoutDirection: Int, - resourceId: Int, - ): Flow<Int> { + /** Emits the dimensional pixel size of the given resource, inverting it for RTL if necessary */ + fun directionalDimensionPixelSize(originLayoutDirection: Int, resourceId: Int): Flow<Int> { return dimensionPixelSize(resourceId).combine(layoutDirection) { size, direction -> if (originLayoutDirection == direction) size else -size } } - override fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>> { + /** Given a set of [resourceId]s, emit Map<ResourceId, DimensionPixelSize> on config change */ + fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>> { return onAnyConfigurationChange.mapLatest { resourceIds.associateWith { repository.getDimensionPixelSize(it) } } } - override val onAnyConfigurationChange: Flow<Unit> = + /** Emit an event on any config change */ + val onAnyConfigurationChange: Flow<Unit> = repository.onAnyConfigurationChange.onStart { emit(Unit) } - override val configurationValues: Flow<Configuration> = repository.configurationValues + /** Emits the new configuration on any configuration change */ + val configurationValues: Flow<Configuration> = repository.configurationValues - override val scaleForResolution: Flow<Float> = repository.scaleForResolution + /** Emits the current resolution scaling factor */ + val scaleForResolution: Flow<Float> = repository.scaleForResolution } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index ca4bbc0ae3bd..00c62092421d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -33,6 +33,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.theme.PlatformTheme import com.android.internal.logging.UiEventLogger import com.android.systemui.Flags.communalEditWidgetsActivityFinishFix @@ -44,6 +45,7 @@ import com.android.systemui.communal.ui.compose.CommunalHub import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger @@ -52,13 +54,13 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.settings.UserTracker import javax.inject.Inject import kotlinx.coroutines.flow.first -import com.android.app.tracing.coroutines.launchTraced as launch /** An Activity for editing the widgets that appear in hub mode. */ class EditWidgetsActivity @Inject constructor( private val communalViewModel: CommunalEditModeViewModel, + private val keyguardInteractor: KeyguardInteractor, private var windowManagerService: IWindowManager? = null, private val uiEventLogger: UiEventLogger, private val widgetConfiguratorFactory: WidgetConfigurationController.Factory, @@ -223,6 +225,9 @@ constructor( communalViewModel.currentScene.first { it == CommunalScenes.Blank } } + // Wait for dream to exit, if we were previously dreaming. + keyguardInteractor.isDreaming.first { !it } + communalViewModel.setEditModeState(EditModeState.SHOWING) // Inform the ActivityController that we are now fully visible. diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 4447dff7af00..9138243c642c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -48,7 +48,7 @@ import com.android.systemui.brightness.dagger.ScreenBrightnessModule; 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.ConfigurationModule; +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; @@ -212,7 +212,7 @@ import javax.inject.Named; ClockRegistryModule.class, CommunalModule.class, CommonDataLayerModule.class, - ConfigurationModule.class, + ConfigurationStateModule.class, ConfigurationRepositoryModule.class, CommonUsageStatsDataLayerModule.class, ConfigurationControllerModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java index 303f91688575..911331b8bff1 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java @@ -42,7 +42,7 @@ import androidx.annotation.Nullable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.GlobalSettings; import java.io.PrintWriter; @@ -51,6 +51,7 @@ import java.util.Map; import java.util.Objects; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; import java.util.function.Consumer; import javax.inject.Inject; @@ -74,7 +75,6 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { static final String TAG = "SysUIFlags"; private final FlagManager mFlagManager; - private final Context mContext; private final GlobalSettings mGlobalSettings; private final Resources mResources; private final SystemPropertiesHelper mSystemProperties; @@ -84,6 +84,8 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { private final Map<String, String> mStringFlagCache = new ConcurrentHashMap<>(); private final Map<String, Integer> mIntFlagCache = new ConcurrentHashMap<>(); private final Restarter mRestarter; + private final UserTracker mUserTracker; + private final Executor mMainExecutor; private final ServerFlagReader.ChangeListener mOnPropertiesChanged = new ServerFlagReader.ChangeListener() { @@ -118,6 +120,18 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { } }; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + mContext.unregisterReceiver(mReceiver); + mContext = userContext; + registerReceiver(); + } + }; + + private Context mContext; + @Inject public FeatureFlagsClassicDebug( FlagManager flagManager, @@ -128,10 +142,11 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { ServerFlagReader serverFlagReader, @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags, Restarter restarter, - UserContextProvider userContextProvider) { + UserTracker userTracker, + @Main Executor executor) { mFlagManager = flagManager; if (classicFlagsMultiUser()) { - mContext = userContextProvider.createCurrentUserContext(context); + mContext = userTracker.createCurrentUserContext(context); } else { mContext = context; } @@ -141,19 +156,28 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { mServerFlagReader = serverFlagReader; mAllFlags = allFlags; mRestarter = restarter; + mUserTracker = userTracker; + mMainExecutor = executor; } /** Call after construction to setup listeners. */ void init() { - IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_SET_FLAG); - filter.addAction(ACTION_GET_FLAGS); mFlagManager.setOnSettingsChangedAction( suppressRestart -> restartSystemUI(suppressRestart, "Settings changed")); mFlagManager.setClearCacheAction(this::removeFromCache); + registerReceiver(); + if (classicFlagsMultiUser()) { + mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); + } + mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged); + } + + void registerReceiver() { + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_SET_FLAG); + filter.addAction(ACTION_GET_FLAGS); mContext.registerReceiver(mReceiver, filter, null, null, Context.RECEIVER_EXPORTED_UNAUDITED); - mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt index d4adcdd49f5c..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 @@ -37,7 +37,6 @@ import com.android.systemui.qs.QSImpl import com.android.systemui.qs.dagger.QSSceneComponent import com.android.systemui.res.R import com.android.systemui.settings.brightness.MirrorController -import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.util.kotlin.sample @@ -207,7 +206,7 @@ constructor( dumpManager: DumpManager, @Main private val mainDispatcher: CoroutineDispatcher, @Application applicationScope: CoroutineScope, - @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor, + private val configurationInteractor: ConfigurationInteractor, private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater, ) : QSContainerController, QSSceneAdapter, Dumpable { @@ -220,7 +219,7 @@ constructor( dumpManager: DumpManager, @Main dispatcher: CoroutineDispatcher, @Application scope: CoroutineScope, - @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, + configurationInteractor: ConfigurationInteractor, ) : this( qsSceneComponentFactory, qsImplProvider, @@ -257,7 +256,7 @@ constructor( .stateIn( applicationScope, SharingStarted.WhileSubscribed(), - customizerState.value.isShowing, + customizerState.value.isShowing ) override val customizerAnimationDuration: StateFlow<Int> = customizerState diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt index 42d4effbac3a..6fb9b1fe8873 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt @@ -25,8 +25,6 @@ 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.common.ui.domain.interactor.ConfigurationInteractor -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl import com.android.systemui.dagger.SysUISingleton import com.android.systemui.res.R import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround @@ -135,18 +133,4 @@ object ShadeDisplayAwareModule { globalConfigurationRepository } } - - @SysUISingleton - @Provides - @ShadeDisplayAware - fun provideShadeAwareConfigurationInteractor( - @ShadeDisplayAware configurationRepository: ConfigurationRepository, - @GlobalConfig configurationInteractor: ConfigurationInteractor, - ): ConfigurationInteractor { - return if (ShadeWindowGoesAround.isEnabled) { - ConfigurationInteractorImpl(configurationRepository) - } else { - configurationInteractor - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt index 752674854e2d..c8d3f339b3e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt @@ -68,8 +68,13 @@ constructor( notifChipsInteractor.onPromotedNotificationChipTapped(this@toChipModel.key) } } - return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) - // TODO(b/364653005): Use Notification.showWhen to determine if we should show the time. + return OngoingActivityChipModel.Shown.ShortTimeDelta( + icon, + colors, + time = this.whenTime, + onClickListener, + ) + // TODO(b/364653005): If Notification.showWhen = false, don't show the time delta. // TODO(b/364653005): If Notification.whenTime is in the past, show "ago" in the text. // TODO(b/364653005): If Notification.shortCriticalText is set, use that instead of `when`. // TODO(b/364653005): If the app that posted the notification is in the foreground, don't diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/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/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt index f876003926a1..7e0e5f39c708 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt @@ -20,4 +20,4 @@ import com.android.systemui.common.ui.data.repository.configurationRepository import com.android.systemui.kosmos.Kosmos var Kosmos.configurationInteractor: ConfigurationInteractor by - Kosmos.Fixture { ConfigurationInteractorImpl(configurationRepository) } + Kosmos.Fixture { ConfigurationInteractor(configurationRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt index 4a6e27331efc..8c4ec4c2cb75 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -77,7 +77,7 @@ object KeyguardInteractorFactory { repository = repository, powerInteractor = powerInteractor, bouncerRepository = bouncerRepository, - configurationInteractor = ConfigurationInteractorImpl(configurationRepository), + configurationInteractor = ConfigurationInteractor(configurationRepository), shadeRepository = shadeRepository, keyguardTransitionInteractor = keyguardTransitionInteractor, sceneInteractorProvider = { sceneInteractor }, diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh index 672c685aa6d7..1910100a7f5d 100755 --- a/ravenwood/scripts/run-ravenwood-tests.sh +++ b/ravenwood/scripts/run-ravenwood-tests.sh @@ -26,7 +26,7 @@ # Regex to identify slow tests, in PCRE -SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood)$' +SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood|CarSystemUIRavenTests)$' smoke=0 include_re="" @@ -67,7 +67,7 @@ filter() { if [[ "$re" == "" ]] ; then cat # No filtering else - grep $grep_arg -P "$re" + grep $grep_arg -iP "$re" fi } diff --git a/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/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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 6cdab3f51f85..4ce18d232936 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3551,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); @@ -23332,49 +23372,83 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); } - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); + if (Flags.setMtePolicyCoexistence()) { + enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(), + UserHandle.USER_ALL); + } else { + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(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); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller) - || isSystemUid(caller)); + if (Flags.setMtePolicyCoexistence()) { + enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(), + UserHandle.USER_ALL); + } else { + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(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; + } } } @@ -23736,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. } 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 6cb1756f93eb..f1711f5f8c0b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -337,6 +337,13 @@ final class PolicyDefinition<V> { PolicyEnforcerCallbacks::noOp, new PackageSetPolicySerializer()); + static PolicyDefinition<Integer> MEMORY_TAGGING = new PolicyDefinition<>( + new NoArgsPolicyKey( + DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY), + new TopPriority<>(List.of(EnforcingAdmin.DPC_AUTHORITY)), + PolicyEnforcerCallbacks::setMtePolicy, + new IntegerPolicySerializer()); + private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>(); private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>(); @@ -383,6 +390,8 @@ final class PolicyDefinition<V> { PASSWORD_COMPLEXITY); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PACKAGES_SUSPENDED_POLICY, PACKAGES_SUSPENDED); + POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY, + MEMORY_TAGGING); // User Restriction Policies USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 145416215cb0..fdc0ec1a0471 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -47,6 +47,7 @@ import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.UserHandle; import android.permission.AdminPermissionControlParams; import android.permission.PermissionControllerManager; @@ -210,6 +211,7 @@ final class PolicyEnforcerCallbacks { private static class BlockingCallback { private final CountDownLatch mLatch = new CountDownLatch(1); private final AtomicReference<Boolean> mValue = new AtomicReference<>(); + public void trigger(Boolean value) { mValue.set(value); mLatch.countDown(); @@ -435,4 +437,43 @@ final class PolicyEnforcerCallbacks { return AndroidFuture.completedFuture(true); }); } + + static CompletableFuture<Boolean> setMtePolicy( + @Nullable Integer mtePolicy, @NonNull Context context, int userId, + @NonNull PolicyKey policyKey) { + if (mtePolicy == null) { + mtePolicy = DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY; + } + final Set<Integer> allowedModes = + Set.of( + DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY, + DevicePolicyManager.MTE_DISABLED, + DevicePolicyManager.MTE_ENABLED); + if (!allowedModes.contains(mtePolicy)) { + Slog.wtf(LOG_TAG, "MTE policy is not a known one: " + mtePolicy); + return AndroidFuture.completedFuture(false); + } + + final String mteDpmSystemProperty = + "ro.arm64.memtag.bootctl_device_policy_manager"; + final String mteSettingsSystemProperty = + "ro.arm64.memtag.bootctl_settings_toggle"; + final String mteControlProperty = "arm64.memtag.bootctl"; + + final boolean isAvailable = SystemProperties.getBoolean(mteDpmSystemProperty, + SystemProperties.getBoolean(mteSettingsSystemProperty, false)); + if (!isAvailable) { + return AndroidFuture.completedFuture(false); + } + + if (mtePolicy == DevicePolicyManager.MTE_ENABLED) { + SystemProperties.set(mteControlProperty, "memtag"); + } else if (mtePolicy == DevicePolicyManager.MTE_DISABLED) { + SystemProperties.set(mteControlProperty, "memtag-off"); + } else if (mtePolicy == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) { + SystemProperties.set(mteControlProperty, "default"); + } + + return AndroidFuture.completedFuture(true); + } } diff --git a/services/tests/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/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( |