diff options
281 files changed, 6730 insertions, 5449 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/aconfig/device_idle.aconfig b/apex/jobscheduler/service/aconfig/device_idle.aconfig index c4d0d1850a18..426031fbeb9c 100644 --- a/apex/jobscheduler/service/aconfig/device_idle.aconfig +++ b/apex/jobscheduler/service/aconfig/device_idle.aconfig @@ -17,3 +17,13 @@ flag { description: "Disable wakelocks for background apps while Light Device Idle is active" bug: "326607666" } + +flag { + name: "use_cpu_time_for_temp_allowlist" + namespace: "backstage_power" + description: "Use CPU time for temporary allowlists" + bug: "376561328" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 3e650da2e66f..41fd4a29cfd1 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -620,8 +620,8 @@ public class DeviceIdleController extends SystemService * the network and acquire wakelocks. Times are in milliseconds. */ @GuardedBy("this") - private final SparseArray<Pair<MutableLong, String>> mTempWhitelistAppIdEndTimes - = new SparseArray<>(); + @VisibleForTesting + final SparseArray<Pair<MutableLong, String>> mTempWhitelistAppIdEndTimes = new SparseArray<>(); private NetworkPolicyManagerInternal mNetworkPolicyManagerInternal; @@ -1941,7 +1941,8 @@ public class DeviceIdleController extends SystemService private static final int MSG_REPORT_IDLE_ON_LIGHT = 3; private static final int MSG_REPORT_IDLE_OFF = 4; private static final int MSG_REPORT_ACTIVE = 5; - private static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6; + @VisibleForTesting + static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6; @VisibleForTesting static final int MSG_REPORT_STATIONARY_STATUS = 7; private static final int MSG_FINISH_IDLE_OP = 8; @@ -2511,6 +2512,11 @@ public class DeviceIdleController extends SystemService return SystemClock.elapsedRealtime(); } + /** Returns the current elapsed realtime in milliseconds. */ + long getUptimeMillis() { + return SystemClock.uptimeMillis(); + } + LocationManager getLocationManager() { if (mLocationManager == null) { mLocationManager = mContext.getSystemService(LocationManager.class); @@ -3264,7 +3270,8 @@ public class DeviceIdleController extends SystemService void addPowerSaveTempWhitelistAppDirectInternal(int callingUid, int uid, long duration, @TempAllowListType int tempAllowListType, boolean sync, @ReasonCode int reasonCode, @Nullable String reason) { - final long timeNow = SystemClock.elapsedRealtime(); + final long timeNow = Flags.useCpuTimeForTempAllowlist() ? mInjector.getUptimeMillis() + : mInjector.getElapsedRealtime(); boolean informWhitelistChanged = false; int appId = UserHandle.getAppId(uid); synchronized (this) { @@ -3350,7 +3357,8 @@ public class DeviceIdleController extends SystemService } void checkTempAppWhitelistTimeout(int uid) { - final long timeNow = SystemClock.elapsedRealtime(); + final long timeNow = Flags.useCpuTimeForTempAllowlist() ? mInjector.getUptimeMillis() + : mInjector.getElapsedRealtime(); final int appId = UserHandle.getAppId(uid); if (DEBUG) { Slog.d(TAG, "checkTempAppWhitelistTimeout: uid=" + uid + ", timeNow=" + timeNow); @@ -5219,6 +5227,17 @@ public class DeviceIdleController extends SystemService } } + pw.println(" Flags:"); + pw.print(" "); + pw.print(Flags.FLAG_USE_CPU_TIME_FOR_TEMP_ALLOWLIST); + pw.print("="); + pw.println(Flags.useCpuTimeForTempAllowlist()); + pw.print(" "); + pw.print(Flags.FLAG_REMOVE_IDLE_LOCATION); + pw.print("="); + pw.println(Flags.removeIdleLocation()); + pw.println(); + synchronized (this) { mConstants.dump(pw); @@ -5449,7 +5468,8 @@ public class DeviceIdleController extends SystemService pw.println(" Temp whitelist schedule:"); prefix = " "; } - final long timeNow = SystemClock.elapsedRealtime(); + final long timeNow = Flags.useCpuTimeForTempAllowlist() ? mInjector.getUptimeMillis() + : mInjector.getElapsedRealtime(); for (int i = 0; i < size; i++) { pw.print(prefix); pw.print("UID="); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index ba8e3e8b48fc..8f44698ffd8d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -1550,7 +1550,7 @@ class JobConcurrencyManager { mActivePkgStats.add( jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), packageStats); - mService.resetPendingJobReasonCache(jobStatus); + mService.resetPendingJobReasonsCache(jobStatus); } if (mService.getPendingJobQueue().remove(jobStatus)) { mService.mJobPackageTracker.noteNonpending(jobStatus); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 4c1951ae9d39..f569388ef3c1 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -460,10 +460,10 @@ public class JobSchedulerService extends com.android.server.SystemService private final ArraySet<JobStatus> mChangedJobList = new ArraySet<>(); /** - * Cached pending job reasons. Mapping from UID -> namespace -> job ID -> reason. + * Cached pending job reasons. Mapping from UID -> namespace -> job ID -> reasons. */ - @GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing - private final SparseArrayMap<String, SparseIntArray> mPendingJobReasonCache = + @GuardedBy("mPendingJobReasonsCache") // Use its own lock to avoid blocking JS processing + private final SparseArrayMap<String, SparseArray<int[]>> mPendingJobReasonsCache = new SparseArrayMap<>(); /** @@ -2021,139 +2021,123 @@ public class JobSchedulerService extends com.android.server.SystemService } } - @JobScheduler.PendingJobReason - private int getPendingJobReason(int uid, String namespace, int jobId) { - int reason; + @NonNull + private int[] getPendingJobReasons(int uid, String namespace, int jobId) { + int[] reasons; // Some apps may attempt to query this frequently, so cache the reason under a separate lock // so that the rest of JS processing isn't negatively impacted. - synchronized (mPendingJobReasonCache) { - SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace); - if (jobIdToReason != null) { - reason = jobIdToReason.get(jobId, JobScheduler.PENDING_JOB_REASON_UNDEFINED); - if (reason != JobScheduler.PENDING_JOB_REASON_UNDEFINED) { - return reason; + synchronized (mPendingJobReasonsCache) { + SparseArray<int[]> jobIdToReasons = mPendingJobReasonsCache.get(uid, namespace); + if (jobIdToReasons != null) { + reasons = jobIdToReasons.get(jobId); + if (reasons != null) { + return reasons; } } } synchronized (mLock) { - reason = getPendingJobReasonLocked(uid, namespace, jobId); + reasons = getPendingJobReasonsLocked(uid, namespace, jobId); if (DEBUG) { - Slog.v(TAG, "getPendingJobReason(" - + uid + "," + namespace + "," + jobId + ")=" + reason); + Slog.v(TAG, "getPendingJobReasons(" + + uid + "," + namespace + "," + jobId + ")=" + Arrays.toString(reasons)); } } - synchronized (mPendingJobReasonCache) { - SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace); - if (jobIdToReason == null) { - jobIdToReason = new SparseIntArray(); - mPendingJobReasonCache.add(uid, namespace, jobIdToReason); + synchronized (mPendingJobReasonsCache) { + SparseArray<int[]> jobIdToReasons = mPendingJobReasonsCache.get(uid, namespace); + if (jobIdToReasons == null) { + jobIdToReasons = new SparseArray<>(); + mPendingJobReasonsCache.add(uid, namespace, jobIdToReasons); } - jobIdToReason.put(jobId, reason); + jobIdToReasons.put(jobId, reasons); } - return reason; + return reasons; } @VisibleForTesting @JobScheduler.PendingJobReason int getPendingJobReason(JobStatus job) { - return getPendingJobReason(job.getUid(), job.getNamespace(), job.getJobId()); + // keep original method to enable unit testing with flags + return getPendingJobReasons(job.getUid(), job.getNamespace(), job.getJobId())[0]; + } + + @VisibleForTesting + @NonNull + int[] getPendingJobReasons(JobStatus job) { + return getPendingJobReasons(job.getUid(), job.getNamespace(), job.getJobId()); } - @JobScheduler.PendingJobReason @GuardedBy("mLock") - private int getPendingJobReasonLocked(int uid, String namespace, int jobId) { + @NonNull + private int[] getPendingJobReasonsLocked(int uid, String namespace, int jobId) { // Very similar code to isReadyToBeExecutedLocked. - - JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId); + final JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId); if (job == null) { // Job doesn't exist. - return JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID; + return new int[] { JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID }; } - if (isCurrentlyRunningLocked(job)) { - return JobScheduler.PENDING_JOB_REASON_EXECUTING; + return new int[] { JobScheduler.PENDING_JOB_REASON_EXECUTING }; } + final String debugPrefix = "getPendingJobReasonsLocked: " + job.toShortString(); final boolean jobReady = job.isReady(); - if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " ready=" + jobReady); + Slog.v(TAG, debugPrefix + " ready=" + jobReady); } - if (!jobReady) { - return job.getPendingJobReason(); + return job.getPendingJobReasons(); } final boolean userStarted = areUsersStartedLocked(job); - if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " userStarted=" + userStarted); + Slog.v(TAG, debugPrefix + " userStarted=" + userStarted); } if (!userStarted) { - return JobScheduler.PENDING_JOB_REASON_USER; + return new int[] { JobScheduler.PENDING_JOB_REASON_USER }; } final boolean backingUp = mBackingUpUids.get(job.getSourceUid()); if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " backingUp=" + backingUp); + Slog.v(TAG, debugPrefix + " backingUp=" + backingUp); } - if (backingUp) { // TODO: Should we make a special reason for this? - return JobScheduler.PENDING_JOB_REASON_APP; + return new int[] { JobScheduler.PENDING_JOB_REASON_APP }; } - JobRestriction restriction = checkIfRestricted(job); + final JobRestriction restriction = checkIfRestricted(job); if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " restriction=" + restriction); + Slog.v(TAG, debugPrefix + " restriction=" + restriction); } if (restriction != null) { - return restriction.getPendingReason(); + // Currently this will return _DEVICE_STATE because of thermal reasons. + // TODO (b/372031023): does it make sense to move this along with the + // pendingJobReasons() call above and also get the pending reasons from + // all of the restriction controllers? + return new int[] { restriction.getPendingReason() }; } - // The following can be a little more expensive (especially jobActive, since we need to - // go through the array of all potentially active jobs), so we are doing them - // later... but still before checking with the package manager! + // The following can be a little more expensive, so we are doing it later, + // but still before checking with the package manager! final boolean jobPending = mPendingJobQueue.contains(job); - - if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " pending=" + jobPending); + Slog.v(TAG, debugPrefix + " pending=" + jobPending); } - if (jobPending) { - // We haven't started the job for some reason. Presumably, there are too many jobs - // running. - return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE; - } - - final boolean jobActive = mConcurrencyManager.isJobRunningLocked(job); - - if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " active=" + jobActive); - } - if (jobActive) { - return JobScheduler.PENDING_JOB_REASON_UNDEFINED; + // We haven't started the job - presumably, there are too many jobs running. + return new int[] { JobScheduler.PENDING_JOB_REASON_DEVICE_STATE }; } // Validate that the defined package+service is still present & viable. final boolean componentUsable = isComponentUsable(job); - if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " componentUsable=" + componentUsable); + Slog.v(TAG, debugPrefix + " componentUsable=" + componentUsable); } if (!componentUsable) { - return JobScheduler.PENDING_JOB_REASON_APP; + return new int[] { JobScheduler.PENDING_JOB_REASON_APP }; } - return JobScheduler.PENDING_JOB_REASON_UNDEFINED; + return new int[] { JobScheduler.PENDING_JOB_REASON_UNDEFINED }; } private JobInfo getPendingJob(int uid, @Nullable String namespace, int jobId) { @@ -2195,15 +2179,16 @@ public class JobSchedulerService extends com.android.server.SystemService // The app process will be killed soon. There's no point keeping its jobs in // the pending queue to try and start them. if (mPendingJobQueue.remove(job)) { - synchronized (mPendingJobReasonCache) { - SparseIntArray jobIdToReason = mPendingJobReasonCache.get( + synchronized (mPendingJobReasonsCache) { + SparseArray<int[]> jobIdToReason = mPendingJobReasonsCache.get( job.getUid(), job.getNamespace()); if (jobIdToReason == null) { - jobIdToReason = new SparseIntArray(); - mPendingJobReasonCache.add(job.getUid(), job.getNamespace(), + jobIdToReason = new SparseArray<>(); + mPendingJobReasonsCache.add(job.getUid(), job.getNamespace(), jobIdToReason); } - jobIdToReason.put(job.getJobId(), JobScheduler.PENDING_JOB_REASON_USER); + jobIdToReason.put(job.getJobId(), + new int[] { JobScheduler.PENDING_JOB_REASON_USER }); } } } @@ -2229,8 +2214,8 @@ public class JobSchedulerService extends com.android.server.SystemService synchronized (mLock) { mJobs.removeJobsOfUnlistedUsers(umi.getUserIds()); } - synchronized (mPendingJobReasonCache) { - mPendingJobReasonCache.clear(); + synchronized (mPendingJobReasonsCache) { + mPendingJobReasonsCache.clear(); } } @@ -2875,7 +2860,7 @@ public class JobSchedulerService extends com.android.server.SystemService final boolean update = lastJob != null; mJobs.add(jobStatus); // Clear potentially cached INVALID_JOB_ID reason. - resetPendingJobReasonCache(jobStatus); + resetPendingJobReasonsCache(jobStatus); if (mReadyToRock) { for (int i = 0; i < mControllers.size(); i++) { StateController controller = mControllers.get(i); @@ -2897,9 +2882,9 @@ public class JobSchedulerService extends com.android.server.SystemService // Deal with any remaining work items in the old job. jobStatus.stopTrackingJobLocked(incomingJob); - synchronized (mPendingJobReasonCache) { - SparseIntArray reasonCache = - mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace()); + synchronized (mPendingJobReasonsCache) { + SparseArray<int[]> reasonCache = + mPendingJobReasonsCache.get(jobStatus.getUid(), jobStatus.getNamespace()); if (reasonCache != null) { reasonCache.delete(jobStatus.getJobId()); } @@ -2927,11 +2912,11 @@ public class JobSchedulerService extends com.android.server.SystemService return removed; } - /** Remove the pending job reason for this job from the cache. */ - void resetPendingJobReasonCache(@NonNull JobStatus jobStatus) { - synchronized (mPendingJobReasonCache) { - final SparseIntArray reasons = - mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace()); + /** Remove the pending job reasons for this job from the cache. */ + void resetPendingJobReasonsCache(@NonNull JobStatus jobStatus) { + synchronized (mPendingJobReasonsCache) { + final SparseArray<int[]> reasons = + mPendingJobReasonsCache.get(jobStatus.getUid(), jobStatus.getNamespace()); if (reasons != null) { reasons.delete(jobStatus.getJobId()); } @@ -3313,18 +3298,18 @@ public class JobSchedulerService extends com.android.server.SystemService public void onControllerStateChanged(@Nullable ArraySet<JobStatus> changedJobs) { if (changedJobs == null) { mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); - synchronized (mPendingJobReasonCache) { - mPendingJobReasonCache.clear(); + synchronized (mPendingJobReasonsCache) { + mPendingJobReasonsCache.clear(); } } else if (changedJobs.size() > 0) { synchronized (mLock) { mChangedJobList.addAll(changedJobs); } mHandler.obtainMessage(MSG_CHECK_CHANGED_JOB_LIST).sendToTarget(); - synchronized (mPendingJobReasonCache) { + synchronized (mPendingJobReasonsCache) { for (int i = changedJobs.size() - 1; i >= 0; --i) { final JobStatus job = changedJobs.valueAt(i); - resetPendingJobReasonCache(job); + resetPendingJobReasonsCache(job); } } } @@ -3893,23 +3878,21 @@ public class JobSchedulerService extends com.android.server.SystemService // Update the pending reason for any jobs that aren't going to be run. final int numRunnableJobs = runnableJobs.size(); if (numRunnableJobs > 0 && numRunnableJobs != jobsToRun.size()) { - synchronized (mPendingJobReasonCache) { + synchronized (mPendingJobReasonsCache) { for (int i = 0; i < numRunnableJobs; ++i) { final JobStatus job = runnableJobs.get(i); if (jobsToRun.contains(job)) { - // We're running this job. Skip updating the pending reason. - continue; + continue; // we're running this job - skip updating the pending reason. } - SparseIntArray reasons = - mPendingJobReasonCache.get(job.getUid(), job.getNamespace()); + SparseArray<int[]> reasons = + mPendingJobReasonsCache.get(job.getUid(), job.getNamespace()); if (reasons == null) { - reasons = new SparseIntArray(); - mPendingJobReasonCache.add(job.getUid(), job.getNamespace(), reasons); + reasons = new SparseArray<>(); + mPendingJobReasonsCache.add(job.getUid(), job.getNamespace(), reasons); } - // We're force batching these jobs, so consider it an optimization - // policy reason. - reasons.put(job.getJobId(), - JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION); + // we're force batching these jobs - note it as optimization. + reasons.put(job.getJobId(), new int[] + { JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION }); } } } @@ -5123,12 +5106,16 @@ public class JobSchedulerService extends com.android.server.SystemService @Override public int getPendingJobReason(String namespace, int jobId) throws RemoteException { - final int uid = Binder.getCallingUid(); + return getPendingJobReasons(validateNamespace(namespace), jobId)[0]; + } + @Override + public int[] getPendingJobReasons(String namespace, int jobId) throws RemoteException { + final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - return JobSchedulerService.this.getPendingJobReason( - uid, validateNamespace(namespace), jobId); + return JobSchedulerService.this.getPendingJobReasons( + uid, validateNamespace(namespace), jobId); } finally { Binder.restoreCallingIdentity(ident); } @@ -5867,6 +5854,9 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND, android.app.job.Flags.ignoreImportantWhileForeground()); pw.println(); + pw.print(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API, + android.app.job.Flags.getPendingJobReasonsApi()); + pw.println(); pw.decreaseIndent(); pw.println(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java index 68303e86055c..a4a302450849 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java @@ -439,6 +439,9 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { case android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND: pw.println(android.app.job.Flags.ignoreImportantWhileForeground()); break; + case android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API: + pw.println(android.app.job.Flags.getPendingJobReasonsApi()); + break; default: pw.println("Unknown flag: " + flagName); break; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 1dc5a714337c..58579eb0db47 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -2067,12 +2067,11 @@ public final class JobStatus { } /** - * If {@link #isReady()} returns false, this will return a single reason why the job isn't - * ready. If {@link #isReady()} returns true, this will return - * {@link JobScheduler#PENDING_JOB_REASON_UNDEFINED}. + * This will return all potential reasons why the job is pending. */ - @JobScheduler.PendingJobReason - public int getPendingJobReason() { + @NonNull + public int[] getPendingJobReasons() { + final ArrayList<Integer> reasons = new ArrayList<>(); final int unsatisfiedConstraints = ~satisfiedConstraints & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS); if ((CONSTRAINT_BACKGROUND_NOT_RESTRICTED & unsatisfiedConstraints) != 0) { @@ -2084,78 +2083,99 @@ public final class JobStatus { // (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of // battery saver state. if (mIsUserBgRestricted) { - return JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION); + } else { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE); + } + } + if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) { + if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE)) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE); } - return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE; } + if ((CONSTRAINT_BATTERY_NOT_LOW & unsatisfiedConstraints) != 0) { if ((CONSTRAINT_BATTERY_NOT_LOW & requiredConstraints) != 0) { // The developer requested this constraint, so it makes sense to return the // explicit constraint reason. - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW); + } else { + // Hard-coding right now since the current dynamic constraint sets don't overlap + // TODO: return based on active dynamic constraint sets when they start overlapping + reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY); } - // Hard-coding right now since the current dynamic constraint sets don't overlap - // TODO: return based on active dynamic constraint sets when they start overlapping - return JobScheduler.PENDING_JOB_REASON_APP_STANDBY; } if ((CONSTRAINT_CHARGING & unsatisfiedConstraints) != 0) { if ((CONSTRAINT_CHARGING & requiredConstraints) != 0) { // The developer requested this constraint, so it makes sense to return the // explicit constraint reason. - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING); + } else { + // Hard-coding right now since the current dynamic constraint sets don't overlap + // TODO: return based on active dynamic constraint sets when they start overlapping + if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_APP_STANDBY)) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY); + } } - // Hard-coding right now since the current dynamic constraint sets don't overlap - // TODO: return based on active dynamic constraint sets when they start overlapping - return JobScheduler.PENDING_JOB_REASON_APP_STANDBY; - } - if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY; - } - if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER; - } - if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE; - } - if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION; } if ((CONSTRAINT_IDLE & unsatisfiedConstraints) != 0) { if ((CONSTRAINT_IDLE & requiredConstraints) != 0) { // The developer requested this constraint, so it makes sense to return the // explicit constraint reason. - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE); + } else { + // Hard-coding right now since the current dynamic constraint sets don't overlap + // TODO: return based on active dynamic constraint sets when they start overlapping + if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_APP_STANDBY)) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY); + } } - // Hard-coding right now since the current dynamic constraint sets don't overlap - // TODO: return based on active dynamic constraint sets when they start overlapping - return JobScheduler.PENDING_JOB_REASON_APP_STANDBY; + } + + if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY); + } + if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER); + } + if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION); } if ((CONSTRAINT_PREFETCH & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH); } if ((CONSTRAINT_STORAGE_NOT_LOW & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW); } if ((CONSTRAINT_TIMING_DELAY & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY); } if ((CONSTRAINT_WITHIN_QUOTA & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_QUOTA; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_QUOTA); } - - if (getEffectiveStandbyBucket() == NEVER_INDEX) { - Slog.wtf(TAG, "App in NEVER bucket querying pending job reason"); - // The user hasn't officially launched this app. - return JobScheduler.PENDING_JOB_REASON_USER; + if (android.app.job.Flags.getPendingJobReasonsApi()) { + if ((CONSTRAINT_DEADLINE & unsatisfiedConstraints) != 0) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEADLINE); + } } - if (serviceProcessName != null) { - return JobScheduler.PENDING_JOB_REASON_APP; + + if (reasons.isEmpty()) { + if (getEffectiveStandbyBucket() == NEVER_INDEX) { + Slog.wtf(TAG, "App in NEVER bucket querying pending job reason"); + // The user hasn't officially launched this app. + reasons.add(JobScheduler.PENDING_JOB_REASON_USER); + } else if (serviceProcessName != null) { + reasons.add(JobScheduler.PENDING_JOB_REASON_APP); + } else { + reasons.add(JobScheduler.PENDING_JOB_REASON_UNDEFINED); + } } - if (!isReady()) { - Slog.wtf(TAG, "Unknown reason job isn't ready"); + final int[] reasonsArr = new int[reasons.size()]; + for (int i = 0; i < reasonsArr.length; i++) { + reasonsArr[i] = reasons.get(i); } - return JobScheduler.PENDING_JOB_REASON_UNDEFINED; + return reasonsArr; } /** @return whether or not the @param constraint is satisfied */ diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist index a413bbd68f60..16f069394639 100644 --- a/config/preloaded-classes-denylist +++ b/config/preloaded-classes-denylist @@ -1,13 +1,16 @@ android.content.AsyncTaskLoader$LoadTask +android.media.MediaCodecInfo$CodecCapabilities$FeatureList android.net.ConnectivityThread$Singleton android.os.FileObserver android.os.NullVibrator +android.permission.PermissionManager +android.provider.MediaStore android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask +android.view.HdrRenderState android.widget.Magnifier +com.android.internal.jank.InteractionJankMonitor$InstanceHolder +com.android.internal.util.LatencyTracker$SLatencyTrackerHolder gov.nist.core.net.DefaultNetworkLayer -android.net.rtp.AudioGroup -android.net.rtp.AudioStream -android.net.rtp.RtpStream java.util.concurrent.ThreadLocalRandom java.util.ImmutableCollections -com.android.internal.jank.InteractionJankMonitor$InstanceHolder +sun.nio.fs.UnixChannelFactory diff --git a/core/api/current.txt b/core/api/current.txt index d9ab273888db..faecbf1990b5 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -241,6 +241,7 @@ package android { field public static final String PROVIDE_REMOTE_CREDENTIALS = "android.permission.PROVIDE_REMOTE_CREDENTIALS"; field @FlaggedApi("android.security.aapm_api") public static final String QUERY_ADVANCED_PROTECTION_MODE = "android.permission.QUERY_ADVANCED_PROTECTION_MODE"; field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES"; + field @FlaggedApi("android.permission.flags.ranging_permission_enabled") public static final String RANGING = "android.permission.RANGING"; field public static final String READ_ASSISTANT_APP_SEARCH_DATA = "android.permission.READ_ASSISTANT_APP_SEARCH_DATA"; field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE"; field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR"; @@ -1074,6 +1075,7 @@ package android { field public static final int layout = 16842994; // 0x10100f2 field public static final int layoutAnimation = 16842988; // 0x10100ec field public static final int layoutDirection = 16843698; // 0x10103b2 + field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int layoutLabel; field public static final int layoutMode = 16843738; // 0x10103da field public static final int layout_above = 16843140; // 0x1010184 field public static final int layout_alignBaseline = 16843142; // 0x1010186 @@ -8023,6 +8025,7 @@ package android.app.admin { field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final String CONTENT_PROTECTION_POLICY = "contentProtection"; field public static final String KEYGUARD_DISABLED_FEATURES_POLICY = "keyguardDisabledFeatures"; field public static final String LOCK_TASK_POLICY = "lockTask"; + field @FlaggedApi("android.app.admin.flags.set_mte_policy_coexistence") public static final String MEMORY_TAGGING_POLICY = "memoryTagging"; field public static final String PACKAGES_SUSPENDED_POLICY = "packagesSuspended"; field public static final String PACKAGE_UNINSTALL_BLOCKED_POLICY = "packageUninstallBlocked"; field public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity"; @@ -9245,6 +9248,7 @@ package android.app.job { method @Nullable public String getNamespace(); method @Nullable public abstract android.app.job.JobInfo getPendingJob(int); method public int getPendingJobReason(int); + method @FlaggedApi("android.app.job.get_pending_job_reasons_api") @NonNull public int[] getPendingJobReasons(int); method @NonNull public java.util.Map<java.lang.String,java.util.List<android.app.job.JobInfo>> getPendingJobsInAllNamespaces(); method public abstract int schedule(@NonNull android.app.job.JobInfo); field public static final int PENDING_JOB_REASON_APP = 1; // 0x1 @@ -9254,6 +9258,7 @@ package android.app.job { field public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5; // 0x5 field public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6; // 0x6 field public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7; // 0x7 + field @FlaggedApi("android.app.job.get_pending_job_reasons_api") public static final int PENDING_JOB_REASON_CONSTRAINT_DEADLINE = 16; // 0x10 field public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8 field public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9; // 0x9 field public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10; // 0xa @@ -9687,10 +9692,8 @@ package android.app.wallpaper { method @Nullable public String getId(); method @Nullable public android.net.Uri getThumbnail(); method @Nullable public CharSequence getTitle(); - method @NonNull public static android.app.wallpaper.WallpaperDescription readFromStream(@NonNull java.io.InputStream) throws java.io.IOException; method @NonNull public android.app.wallpaper.WallpaperDescription.Builder toBuilder(); method public void writeToParcel(@NonNull android.os.Parcel, int); - method public void writeToStream(@NonNull java.io.OutputStream) throws java.io.IOException; field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpaper.WallpaperDescription> CREATOR; } @@ -13701,7 +13704,7 @@ package android.content.pm { field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1 field public static final int FLAG_USE_APP_ZYGOTE = 8; // 0x8 field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40 - field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR, android.Manifest.permission.UWB_RANGING}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10 + field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR, android.Manifest.permission.UWB_RANGING, android.Manifest.permission.RANGING}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10 field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1 field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100 field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8 @@ -16418,6 +16421,7 @@ package android.graphics { field public static final int FLEX_RGBA_8888 = 42; // 0x2a field public static final int FLEX_RGB_888 = 41; // 0x29 field public static final int HEIC = 1212500294; // 0x48454946 + field @FlaggedApi("com.android.internal.camera.flags.camera_heif_gainmap") public static final int HEIC_ULTRAHDR = 4102; // 0x1006 field public static final int JPEG = 256; // 0x100 field public static final int JPEG_R = 4101; // 0x1005 field public static final int NV16 = 16; // 0x10 @@ -16844,6 +16848,7 @@ package android.graphics { field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000 field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000 field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8 + field @FlaggedApi("com.android.text.flags.vertical_text_layout") public static final int VERTICAL_TEXT_FLAG = 4096; // 0x1000 } public enum Paint.Align { @@ -18708,6 +18713,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 @@ -20986,6 +20992,7 @@ package android.inputmethodservice { method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface(); method public android.view.View onCreateInputView(); method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public void onCustomImeSwitcherButtonRequestedVisible(boolean); method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]); method public boolean onEvaluateFullscreenMode(); method @CallSuper public boolean onEvaluateInputViewShown(); @@ -55573,6 +55580,7 @@ package android.view.accessibility { field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH"; field public static final int EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_MAX_LENGTH = 20000; // 0x4e20 field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX"; + field @FlaggedApi("android.view.accessibility.a11y_character_in_window_api") public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY"; field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY"; field public static final int FLAG_PREFETCH_ANCESTORS = 1; // 0x1 field public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 16; // 0x10 @@ -56975,6 +56983,9 @@ package android.view.inputmethod { method public String getExtraValueOf(String); method public int getIconResId(); method @NonNull public String getLanguageTag(); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public CharSequence getLayoutDisplayName(@NonNull android.content.Context, @NonNull android.content.pm.ApplicationInfo); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public CharSequence getLayoutLabelNonLocalized(); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @StringRes public int getLayoutLabelResource(); method @Deprecated @NonNull public String getLocale(); method public String getMode(); method @NonNull public CharSequence getNameOverride(); @@ -56994,6 +57005,8 @@ package android.view.inputmethod { method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(String); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLayoutLabelNonLocalized(@NonNull CharSequence); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLayoutLabelResource(@StringRes int); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean); method @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setPhysicalKeyboardHint(@Nullable android.icu.util.ULocale, @NonNull String); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(String); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 3fde7497a090..2a01ca082832 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -696,6 +696,7 @@ package android.app { field public static final String OPSTR_PLAY_AUDIO = "android:play_audio"; field public static final String OPSTR_POST_NOTIFICATION = "android:post_notification"; field public static final String OPSTR_PROJECT_MEDIA = "android:project_media"; + field @FlaggedApi("android.permission.flags.ranging_permission_enabled") public static final String OPSTR_RANGING = "android:ranging"; field @FlaggedApi("android.view.contentprotection.flags.rapid_clear_notifications_by_listener_app_op_enabled") public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER = "android:rapid_clear_notifications_by_listener"; field public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard"; field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final String OPSTR_READ_HEART_RATE = "android:read_heart_rate"; @@ -5293,13 +5294,19 @@ package android.hardware.display { field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400 } + public abstract static class VirtualDisplay.Callback { + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public void onRequestedBrightnessChanged(@FloatRange(from=0.0f, to=1.0f) float); + } + public final class VirtualDisplayConfig implements android.os.Parcelable { + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDefaultBrightness(); method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @Nullable public android.view.DisplayCutout getDisplayCutout(); method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") public boolean isHomeSupported(); method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") public boolean isIgnoreActivitySizeRestrictions(); } public static final class VirtualDisplayConfig.Builder { + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDefaultBrightness(@FloatRange(from=0.0f, to=1.0f) float); method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCutout(@Nullable android.view.DisplayCutout); method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setHomeSupported(boolean); method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setIgnoreActivitySizeRestrictions(boolean); @@ -7027,7 +7034,7 @@ package android.hardware.soundtrigger { method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAllowMultipleTriggers(boolean); method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAudioCapabilities(int); method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setCaptureRequested(boolean); - method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@Nullable byte[]); + method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@NonNull byte[]); method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setKeyphrases(@NonNull java.util.Collection<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>); } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 36fc65a76d53..1b707f79ab81 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1017,6 +1017,12 @@ public class ActivityManager { public static final int PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL = 1 << 6; /** + * @hide + * Process is guaranteed cpu time (IE. it will not be frozen). + */ + public static final int PROCESS_CAPABILITY_CPU_TIME = 1 << 7; + + /** * @hide all capabilities, the ORing of all flags in {@link ProcessCapability}. * * Don't expose it as TestApi -- we may add new capabilities any time, which could @@ -1028,7 +1034,8 @@ public class ActivityManager { | PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK | PROCESS_CAPABILITY_BFSL | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK - | PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; + | PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL + | PROCESS_CAPABILITY_CPU_TIME; /** * All implicit capabilities. This capability set is currently only used for processes under @@ -1053,6 +1060,7 @@ public class ActivityManager { pw.print((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-'); pw.print((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-'); pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-'); + pw.print((caps & PROCESS_CAPABILITY_CPU_TIME) != 0 ? 'T' : '-'); } /** @hide */ @@ -1065,6 +1073,7 @@ public class ActivityManager { sb.append((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-'); sb.append((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-'); sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-'); + sb.append((caps & PROCESS_CAPABILITY_CPU_TIME) != 0 ? 'T' : '-'); } /** @@ -2764,14 +2773,19 @@ public class ActivityManager { /** * Information of organized child tasks. * + * @deprecated No longer used * @hide */ + @Deprecated public ArrayList<RecentTaskInfo> childrenTaskInfos = new ArrayList<>(); /** * Information about the last snapshot taken for this task. + * + * @deprecated No longer used * @hide */ + @Deprecated public PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData(); public RecentTaskInfo() { @@ -2793,7 +2807,7 @@ public class ActivityManager { lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR); lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR); lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR); - super.readFromParcel(source); + super.readTaskFromParcel(source); } @Override @@ -2804,7 +2818,7 @@ public class ActivityManager { dest.writeTypedObject(lastSnapshotData.taskSize, flags); dest.writeTypedObject(lastSnapshotData.contentInsets, flags); dest.writeTypedObject(lastSnapshotData.bufferSize, flags); - super.writeToParcel(dest, flags); + super.writeTaskToParcel(dest, flags); } public static final @android.annotation.NonNull Creator<RecentTaskInfo> CREATOR @@ -2988,13 +3002,13 @@ public class ActivityManager { public void readFromParcel(Parcel source) { id = source.readInt(); - super.readFromParcel(source); + super.readTaskFromParcel(source); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); - super.writeToParcel(dest, flags); + super.writeTaskToParcel(dest, flags); } public static final @android.annotation.NonNull Creator<RunningTaskInfo> CREATOR = new Creator<RunningTaskInfo>() { diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 799df1f9227a..16dcf2ad7e45 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -534,10 +534,9 @@ public class ActivityTaskManager { dest.writeIntArray(childTaskUserIds); dest.writeInt(visible ? 1 : 0); dest.writeInt(position); - super.writeToParcel(dest, flags); + super.writeTaskToParcel(dest, flags); } - @Override void readFromParcel(Parcel source) { bounds = source.readTypedObject(Rect.CREATOR); childTaskIds = source.createIntArray(); @@ -546,7 +545,7 @@ public class ActivityTaskManager { childTaskUserIds = source.createIntArray(); visible = source.readInt() > 0; position = source.readInt(); - super.readFromParcel(source); + super.readTaskFromParcel(source); } public static final @NonNull Creator<RootTaskInfo> CREATOR = new Creator<>() { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index ca98da76b78f..60b8f80d8f2d 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3100,6 +3100,19 @@ public final class ActivityThread extends ClientTransactionHandler mResourcesManager = ResourcesManager.getInstance(); } + /** + * Creates and initialize a new system activity thread, to be used for testing. This does not + * call {@link #attach}, so it does not modify static state. + */ + @VisibleForTesting + @NonNull + public static ActivityThread createSystemActivityThreadForTesting() { + final var thread = new ActivityThread(); + thread.mSystemThread = true; + initializeSystemThread(thread); + return thread; + } + @UnsupportedAppUsage public ApplicationThread getApplicationThread() { @@ -6806,6 +6819,16 @@ public final class ActivityThread extends ClientTransactionHandler LoadedApk.makePaths(this, resApk.getApplicationInfo(), oldPaths); resApk.updateApplicationInfo(ai, oldPaths); } + if (android.content.res.Flags.systemContextHandleAppInfoChanged() && mSystemThread) { + final var systemContext = getSystemContext(); + if (systemContext.getPackageName().equals(ai.packageName)) { + // The system package is not tracked directly, but still needs to receive updates to + // its application info. + final ArrayList<String> oldPaths = new ArrayList<>(); + LoadedApk.makePaths(this, systemContext.getApplicationInfo(), oldPaths); + systemContext.mPackageInfo.updateApplicationInfo(ai, oldPaths); + } + } ResourcesImpl beforeImpl = getApplication().getResources().getImpl(); @@ -8560,17 +8583,7 @@ public final class ActivityThread extends ClientTransactionHandler // we can't display an alert, we just want to die die die. android.ddm.DdmHandleAppName.setAppName("system_process", UserHandle.myUserId()); - try { - mInstrumentation = new Instrumentation(); - mInstrumentation.basicInit(this); - ContextImpl context = ContextImpl.createAppContext( - this, getSystemContext().mPackageInfo); - mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null); - mInitialApplication.onCreate(); - } catch (Exception e) { - throw new RuntimeException( - "Unable to instantiate Application():" + e.toString(), e); - } + initializeSystemThread(this); } ViewRootImpl.ConfigChangedCallback configChangedCallback = (Configuration globalConfig) -> { @@ -8595,6 +8608,28 @@ public final class ActivityThread extends ClientTransactionHandler ViewRootImpl.addConfigCallback(configChangedCallback); } + /** + * Initializes the given system activity thread, setting up its instrumentation and initial + * application. This only has an effect if the given thread is a {@link #mSystemThread}. + * + * @param thread the given system activity thread to initialize. + */ + private static void initializeSystemThread(@NonNull ActivityThread thread) { + if (!thread.mSystemThread) { + return; + } + try { + thread.mInstrumentation = new Instrumentation(); + thread.mInstrumentation.basicInit(thread); + ContextImpl context = ContextImpl.createAppContext( + thread, thread.getSystemContext().mPackageInfo); + thread.mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null); + thread.mInitialApplication.onCreate(); + } catch (Exception e) { + throw new RuntimeException("Unable to instantiate Application():" + e, e); + } + } + @UnsupportedAppUsage public static ActivityThread systemMain() { ThreadedRenderer.initForSystemProcess(); diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java index 8370c2e522f3..68794588afc5 100644 --- a/core/java/android/app/AppCompatTaskInfo.java +++ b/core/java/android/app/AppCompatTaskInfo.java @@ -167,10 +167,11 @@ public class AppCompatTaskInfo implements Parcelable { } /** - * @return {@code true} if top activity is pillarboxed. + * @return {@code true} if the top activity bounds are letterboxed with width <= height. */ - public boolean isTopActivityPillarboxed() { - return topActivityLetterboxWidth < topActivityLetterboxHeight; + public boolean isTopActivityPillarboxShaped() { + return isTopActivityLetterboxed() + && topActivityLetterboxWidth <= topActivityLetterboxHeight; } /** diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 0629b8a58b42..38c8583dd024 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -63,6 +63,7 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; +import android.os.IpcDataCache; import android.os.Looper; import android.os.PackageTagsList; import android.os.Parcel; @@ -78,12 +79,14 @@ import android.permission.PermissionGroupUsage; import android.permission.PermissionUsageHelper; import android.permission.flags.Flags; import android.provider.DeviceConfig; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.LongSparseArray; import android.util.LongSparseLongArray; import android.util.Pools; import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.Immutable; @@ -1611,9 +1614,16 @@ public class AppOpsManager { /** @hide Access to read skin temperature. */ public static final int OP_READ_SKIN_TEMPERATURE = AppOpEnums.APP_OP_READ_SKIN_TEMPERATURE; + /** + * Allows an app to range with nearby devices using any ranging technology available. + * + * @hide + */ + public static final int OP_RANGING = AppOpEnums.APP_OP_RANGING; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 151; + public static final int _NUM_OP = 152; /** * All app ops represented as strings. @@ -1768,6 +1778,7 @@ public class AppOpsManager { OPSTR_RECEIVE_SENSITIVE_NOTIFICATIONS, OPSTR_READ_HEART_RATE, OPSTR_READ_SKIN_TEMPERATURE, + OPSTR_RANGING, }) public @interface AppOpString {} @@ -2515,6 +2526,11 @@ public class AppOpsManager { @FlaggedApi(Flags.FLAG_PLATFORM_SKIN_TEMPERATURE_ENABLED) public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature"; + /** @hide Access to ranging */ + @SystemApi + @FlaggedApi(Flags.FLAG_RANGING_PERMISSION_ENABLED) + public static final String OPSTR_RANGING = "android:ranging"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -2586,6 +2602,7 @@ public class AppOpsManager { OP_BLUETOOTH_ADVERTISE, OP_UWB_RANGING, OP_NEARBY_WIFI_DEVICES, + Flags.rangingPermissionEnabled() ? OP_RANGING : OP_NONE, // Notifications OP_POST_NOTIFICATION, // Health @@ -3108,6 +3125,10 @@ public class AppOpsManager { Flags.platformSkinTemperatureEnabled() ? HealthPermissions.READ_SKIN_TEMPERATURE : null) .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_RANGING, OPSTR_RANGING, "RANGING") + .setPermission(Flags.rangingPermissionEnabled()? + Manifest.permission.RANGING : null) + .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), }; // The number of longs needed to form a full bitmask of app ops @@ -7797,6 +7818,116 @@ public class AppOpsManager { } } + private static final String APP_OP_MODE_CACHING_API = "getAppOpMode"; + private static final String APP_OP_MODE_CACHING_NAME = "appOpModeCache"; + private static final int APP_OP_MODE_CACHING_SIZE = 2048; + + private static final IpcDataCache.QueryHandler<AppOpModeQuery, Integer> sGetAppOpModeQuery = + new IpcDataCache.QueryHandler<>() { + @Override + public Integer apply(AppOpModeQuery query) { + IAppOpsService service = getService(); + try { + return service.checkOperationRawForDevice(query.op, query.uid, + query.packageName, query.attributionTag, query.virtualDeviceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean shouldBypassCache(@NonNull AppOpModeQuery query) { + // If the flag to enable the new caching behavior is off, bypass the cache. + return !Flags.appopModeCachingEnabled(); + } + }; + + // A LRU cache on binder clients that caches AppOp mode by uid, packageName, virtualDeviceId + // and attributionTag. + private static final IpcDataCache<AppOpModeQuery, Integer> sAppOpModeCache = + new IpcDataCache<>(APP_OP_MODE_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM, + APP_OP_MODE_CACHING_API, APP_OP_MODE_CACHING_NAME, sGetAppOpModeQuery); + + // Ops that we don't want to cache due to: + // 1) Discrepancy of attributionTag support in checkOp and noteOp that determines if a package + // can bypass user restriction of an op: b/240617242. COARSE_LOCATION and FINE_LOCATION are + // the only two ops that are impacted. + private static final SparseBooleanArray OPS_WITHOUT_CACHING = new SparseBooleanArray(); + static { + OPS_WITHOUT_CACHING.put(OP_COARSE_LOCATION, true); + OPS_WITHOUT_CACHING.put(OP_FINE_LOCATION, true); + } + + private static boolean isAppOpModeCachingEnabled(int opCode) { + if (!Flags.appopModeCachingEnabled()) { + return false; + } + return !OPS_WITHOUT_CACHING.get(opCode, false); + } + + /** + * @hide + */ + public static void invalidateAppOpModeCache() { + if (Flags.appopModeCachingEnabled()) { + IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, APP_OP_MODE_CACHING_API); + } + } + + /** + * Bypass AppOpModeCache in the local process + * + * @hide + */ + public static void disableAppOpModeCache() { + if (Flags.appopModeCachingEnabled()) { + sAppOpModeCache.disableLocal(); + } + } + + private static final class AppOpModeQuery { + final int op; + final int uid; + final String packageName; + final int virtualDeviceId; + final String attributionTag; + final String methodName; + + AppOpModeQuery(int op, int uid, @Nullable String packageName, int virtualDeviceId, + @Nullable String attributionTag, @Nullable String methodName) { + this.op = op; + this.uid = uid; + this.packageName = packageName; + this.virtualDeviceId = virtualDeviceId; + this.attributionTag = attributionTag; + this.methodName = methodName; + } + + @Override + public String toString() { + return TextUtils.formatSimple("AppOpModeQuery(op=%d, uid=%d, packageName=%s, " + + "virtualDeviceId=%d, attributionTag=%s, methodName=%s", op, uid, + packageName, virtualDeviceId, attributionTag, methodName); + } + + @Override + public int hashCode() { + return Objects.hash(op, uid, packageName, virtualDeviceId, attributionTag); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null) return false; + if (this.getClass() != o.getClass()) return false; + + AppOpModeQuery other = (AppOpModeQuery) o; + return op == other.op && uid == other.uid && Objects.equals(packageName, + other.packageName) && virtualDeviceId == other.virtualDeviceId + && Objects.equals(attributionTag, other.attributionTag); + } + } + AppOpsManager(Context context, IAppOpsService service) { mContext = context; mService = service; @@ -8851,12 +8982,16 @@ public class AppOpsManager { private int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName, int virtualDeviceId) { try { - if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { - return mService.checkOperationRaw(op, uid, packageName, null); + int mode; + if (isAppOpModeCachingEnabled(op)) { + mode = sAppOpModeCache.query( + new AppOpModeQuery(op, uid, packageName, virtualDeviceId, null, + "unsafeCheckOpRawNoThrow")); } else { - return mService.checkOperationRawForDevice( + mode = mService.checkOperationRawForDevice( op, uid, packageName, null, virtualDeviceId); } + return mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -9041,7 +9176,7 @@ public class AppOpsManager { SyncNotedAppOp syncOp; if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { syncOp = mService.noteOperation(op, uid, packageName, attributionTag, - collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); + collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); } else { syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag, virtualDeviceId, collectionMode == COLLECT_ASYNC, message, @@ -9284,8 +9419,21 @@ public class AppOpsManager { @UnsupportedAppUsage public int checkOp(int op, int uid, String packageName) { try { - int mode = mService.checkOperationForDevice(op, uid, packageName, - Context.DEVICE_ID_DEFAULT); + int mode; + if (isAppOpModeCachingEnabled(op)) { + mode = sAppOpModeCache.query( + new AppOpModeQuery(op, uid, packageName, Context.DEVICE_ID_DEFAULT, null, + "checkOp")); + if (mode == MODE_FOREGROUND) { + // We only cache raw mode. If the mode is FOREGROUND, we need another binder + // call to fetch translated value based on the process state. + mode = mService.checkOperationForDevice(op, uid, packageName, + Context.DEVICE_ID_DEFAULT); + } + } else { + mode = mService.checkOperationForDevice(op, uid, packageName, + Context.DEVICE_ID_DEFAULT); + } if (mode == MODE_ERRORED) { throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); } @@ -9324,13 +9472,19 @@ public class AppOpsManager { private int checkOpNoThrow(int op, int uid, String packageName, int virtualDeviceId) { try { int mode; - if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { - mode = mService.checkOperation(op, uid, packageName); + if (isAppOpModeCachingEnabled(op)) { + mode = sAppOpModeCache.query( + new AppOpModeQuery(op, uid, packageName, virtualDeviceId, null, + "checkOpNoThrow")); + if (mode == MODE_FOREGROUND) { + // We only cache raw mode. If the mode is FOREGROUND, we need another binder + // call to fetch translated value based on the process state. + mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId); + } } else { mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId); } - - return mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : mode; + return mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java index d1e517bbd03c..16444dc5adde 100644 --- a/core/java/android/app/ForegroundServiceTypePolicy.java +++ b/core/java/android/app/ForegroundServiceTypePolicy.java @@ -398,6 +398,7 @@ public abstract class ForegroundServiceTypePolicy { new RegularPermission(Manifest.permission.NFC), new RegularPermission(Manifest.permission.TRANSMIT_IR), new RegularPermission(Manifest.permission.UWB_RANGING), + new RegularPermission(Manifest.permission.RANGING), new UsbDevicePermission(), new UsbAccessoryPermission(), }, false), diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 038dcdb7efc9..f432a22b14ac 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -717,12 +717,10 @@ public class PropertyInvalidatedCache<Query, Result> { // The shared memory. private volatile NonceStore mStore; - // The index of the nonce in shared memory. + // The index of the nonce in shared memory. This changes from INVALID only when the local + // object is completely initialized. private volatile int mHandle = NonceStore.INVALID_NONCE_INDEX; - // True if the string has been stored, ever. - private volatile boolean mRecorded = false; - // A short name that is saved in shared memory. This is the portion of the property name // that follows the prefix. private final String mShortName; @@ -736,48 +734,63 @@ public class PropertyInvalidatedCache<Query, Result> { } } + // Initialize the mStore and mHandle variables. This function does nothing if the + // variables are already initialized. Synchronization ensures that initialization happens + // no more than once. The function returns the new value of mHandle. + // + // If the "update" boolean is true, then the property is registered with the nonce store + // before the associated handle is fetched. + private int initialize(boolean update) { + synchronized (mLock) { + int handle = mHandle; + if (handle == NonceStore.INVALID_NONCE_INDEX) { + if (mStore == null) { + mStore = NonceStore.getInstance(); + if (mStore == null) { + return NonceStore.INVALID_NONCE_INDEX; + } + } + if (update) { + mStore.storeName(mShortName); + } + handle = mStore.getHandleForName(mShortName); + if (handle == NonceStore.INVALID_NONCE_INDEX) { + return NonceStore.INVALID_NONCE_INDEX; + } + // The handle must be valid. + mHandle = handle; + } + return handle; + } + } + // Fetch the nonce from shared memory. If the shared memory is not available, return // UNSET. If the shared memory is available but the nonce name is not known (it may not // have been invalidated by the server yet), return UNSET. @Override long getNonceInternal() { - if (mHandle == NonceStore.INVALID_NONCE_INDEX) { - if (mStore == null) { - mStore = NonceStore.getInstance(); - if (mStore == null) { - return NONCE_UNSET; - } - } - mHandle = mStore.getHandleForName(mShortName); - if (mHandle == NonceStore.INVALID_NONCE_INDEX) { + int handle = mHandle; + if (handle == NonceStore.INVALID_NONCE_INDEX) { + handle = initialize(false); + if (handle == NonceStore.INVALID_NONCE_INDEX) { return NONCE_UNSET; } } - return mStore.getNonce(mHandle); + return mStore.getNonce(handle); } - // Set the nonce in shared mmory. If the shared memory is not available, throw an - // exception. Otherwise, if the nonce name has never been recorded, record it now and - // fetch the handle for the name. If the handle cannot be created, throw an exception. + // Set the nonce in shared memory. If the shared memory is not available or if the nonce + // cannot be registered in shared memory, throw an exception. @Override void setNonceInternal(long value) { - if (mHandle == NonceStore.INVALID_NONCE_INDEX || !mRecorded) { - if (mStore == null) { - mStore = NonceStore.getInstance(); - if (mStore == null) { - throw new IllegalStateException("setNonce: shared memory not ready"); - } - } - // Always store the name before fetching the handle. storeName() is idempotent - // but does take a little time, so this code calls it just once. - mStore.storeName(mShortName); - mRecorded = true; - mHandle = mStore.getHandleForName(mShortName); - if (mHandle == NonceStore.INVALID_NONCE_INDEX) { - throw new IllegalStateException("setNonce: shared memory store failed"); + int handle = mHandle; + if (handle == NonceStore.INVALID_NONCE_INDEX) { + handle = initialize(true); + if (handle == NonceStore.INVALID_NONCE_INDEX) { + throw new IllegalStateException("unable to assign nonce handle: " + mName); } } - mStore.setNonce(mHandle, value); + mStore.setNonce(handle, value); } } diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index c4a6decd982f..aac963ade726 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -364,8 +364,9 @@ public class TaskInfo { // Do nothing } - private TaskInfo(Parcel source) { - readFromParcel(source); + /** @hide */ + public TaskInfo(Parcel source) { + readTaskFromParcel(source); } /** @@ -524,7 +525,7 @@ public class TaskInfo { /** * Reads the TaskInfo from a parcel. */ - void readFromParcel(Parcel source) { + void readTaskFromParcel(Parcel source) { userId = source.readInt(); taskId = source.readInt(); effectiveUid = source.readInt(); @@ -577,8 +578,9 @@ public class TaskInfo { /** * Writes the TaskInfo to a parcel. + * @hide */ - void writeToParcel(Parcel dest, int flags) { + public void writeTaskToParcel(Parcel dest, int flags) { dest.writeInt(userId); dest.writeInt(taskId); dest.writeInt(effectiveUid); diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java index c0e435c04d3c..35149b5a3135 100644 --- a/core/java/android/app/admin/DevicePolicyIdentifiers.java +++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java @@ -191,6 +191,12 @@ public final class DevicePolicyIdentifiers { public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity"; /** + * String identifier for {@link DevicePolicyManager#setMtePolicy(int)}. + */ + @FlaggedApi(android.app.admin.flags.Flags.FLAG_SET_MTE_POLICY_COEXISTENCE) + public static final String MEMORY_TAGGING_POLICY = "memoryTagging"; + + /** * @hide */ public static final String USER_RESTRICTION_PREFIX = "userRestriction_"; diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java index c3d6340be41f..8ffda7242b37 100644 --- a/core/java/android/app/wallpaper/WallpaperDescription.java +++ b/core/java/android/app/wallpaper/WallpaperDescription.java @@ -18,8 +18,6 @@ package android.app.wallpaper; import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING; -import static java.nio.charset.StandardCharsets.UTF_8; - import android.annotation.FlaggedApi; import android.app.WallpaperInfo; import android.content.ComponentName; @@ -31,7 +29,6 @@ import android.text.Html; import android.text.Spanned; import android.text.SpannedString; import android.util.Log; -import android.util.Xml; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -43,8 +40,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -154,46 +149,6 @@ public final class WallpaperDescription implements Parcelable { return Objects.hash(mComponent, mId); } - ////// Stream read/write - - /** - * Writes the content of the {@link WallpaperDescription} to a {@link OutputStream}. - * - * <p>The content can be read by {@link #readFromStream}. This method is intended for use by - * trusted apps only, and the format is not guaranteed to be stable.</p> - */ - public void writeToStream(@NonNull OutputStream outputStream) throws IOException { - TypedXmlSerializer serializer = Xml.newFastSerializer(); - serializer.setOutput(outputStream, UTF_8.name()); - serializer.startTag(null, "description"); - try { - saveToXml(serializer); - } catch (XmlPullParserException e) { - throw new IOException(e); - } - serializer.endTag(null, "description"); - serializer.flush(); - } - - /** - * Reads a {@link PersistableBundle} from an {@link InputStream}. - * - * <p>The stream must be generated by {@link #writeToStream}. This method is intended for use by - * trusted apps only, and the format is not guaranteed to be stable.</p> - */ - @NonNull - public static WallpaperDescription readFromStream(@NonNull InputStream inputStream) - throws IOException { - try { - TypedXmlPullParser parser = Xml.newFastPullParser(); - parser.setInput(inputStream, UTF_8.name()); - parser.next(); - return WallpaperDescription.restoreFromXml(parser); - } catch (XmlPullParserException e) { - throw new IOException(e); - } - } - ////// XML storage /** @hide */ diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 5b0cee75e591..4285b0a2b91a 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -251,6 +251,7 @@ public class ServiceInfo extends ComponentInfo * {@link android.Manifest.permission#NFC}, * {@link android.Manifest.permission#TRANSMIT_IR}, * {@link android.Manifest.permission#UWB_RANGING}, + * {@link android.Manifest.permission#RANGING}, * or has been granted the access to one of the attached USB devices/accessories. */ @RequiresPermission( @@ -267,6 +268,7 @@ public class ServiceInfo extends ComponentInfo Manifest.permission.NFC, Manifest.permission.TRANSMIT_IR, Manifest.permission.UWB_RANGING, + Manifest.permission.RANGING, }, conditional = true ) diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig index e98fc0c9d02c..26ecbd1982d5 100644 --- a/core/java/android/content/res/flags.aconfig +++ b/core/java/android/content/res/flags.aconfig @@ -83,3 +83,15 @@ flag { bug: "364035303" } +flag { + name: "system_context_handle_app_info_changed" + is_exported: true + namespace: "resource_manager" + description: "Feature flag for allowing system context to handle application info changes" + bug: "362420029" + # This flag is read at boot time. + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java index 611738435f7e..1cd9244333c5 100644 --- a/core/java/android/hardware/DataSpace.java +++ b/core/java/android/hardware/DataSpace.java @@ -420,18 +420,38 @@ public final class DataSpace { public static final int DATASPACE_HEIF = 4100; /** - * ISO/IEC TBD + * Ultra HDR * - * JPEG image with embedded recovery map following the Jpeg/R specification. + * JPEG image with embedded HDR gain map following the Ultra HDR specification and + * starting with Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM V} + * ISO/CD 21496‐1 * - * <p>This value must always remain aligned with the public ImageFormat Jpeg/R definition and is - * valid with formats: - * HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to ISO/IEC TBD. - * The image contains a standard SDR JPEG and a recovery map. Jpeg/R decoders can use the - * map to recover the input image.</p> + * <p>This value is valid with formats:</p> + * <ul> + * <li>HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to + * ISO/CD 21496‐1</li> + * </ul> + * <p> + * The image contains a standard SDR JPEG and a gain map. Ultra HDR decoders can use the + * gain map to boost the brightness of the rendered image.</p> */ public static final int DATASPACE_JPEG_R = 4101; + /** + * ISO/IEC 23008-12:2024 + * + * High Efficiency Image File Format (HEIF) with embedded HDR gain map + * + * <p>This value is valid with formats:</p> + * <ul> + * <li>HAL_PIXEL_FORMAT_BLOB: A HEIC image encoded by HEVC encoder + * according to ISO/IEC 23008-12:2024 that includes an HDR gain map and + * metadata according to ISO/CD 21496‐1.</li> + * </ul> + */ + @FlaggedApi(com.android.internal.camera.flags.Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final int DATASPACE_HEIF_ULTRAHDR = 4102; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = { @@ -660,6 +680,7 @@ public final class DataSpace { DATASPACE_DEPTH, DATASPACE_DYNAMIC_DEPTH, DATASPACE_HEIF, + DATASPACE_HEIF_ULTRAHDR, DATASPACE_JPEG_R, DATASPACE_UNKNOWN, DATASPACE_SCRGB_LINEAR, diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 16d82caa6c1a..a37648f7e45d 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -5775,6 +5775,122 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); /** + * <p>The available HEIC (ISO/IEC 23008-12/24) UltraHDR stream + * configurations that this camera device supports + * (i.e. format, width, height, output/input stream).</p> + * <p>The configurations are listed as <code>(format, width, height, input?)</code> tuples.</p> + * <p>All the static, control, and dynamic metadata tags related to JPEG apply to HEIC formats. + * Configuring JPEG and HEIC streams at the same time is not supported.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final Key<android.hardware.camera2.params.StreamConfiguration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS = + new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.heic.availableHeicUltraHdrStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class); + + /** + * <p>This lists the minimum frame duration for each + * format/size combination for HEIC UltraHDR output formats.</p> + * <p>This should correspond to the frame duration when only that + * stream is active, with all processing (typically in android.*.mode) + * set to either OFF or FAST.</p> + * <p>When multiple streams are used in a request, the minimum frame + * duration will be max(individual stream min durations).</p> + * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and + * android.scaler.availableStallDurations for more details about + * calculating the max frame rate.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrMinFrameDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + * <p>This lists the maximum stall duration for each + * output format/size combination for HEIC UltraHDR streams.</p> + * <p>A stall duration is how much extra time would get added + * to the normal minimum frame duration for a repeating request + * that has streams with non-zero stall.</p> + * <p>This functions similarly to + * android.scaler.availableStallDurations for HEIC UltraHDR + * streams.</p> + * <p>All HEIC output stream formats may have a nonzero stall + * duration.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + * <p>The available HEIC (ISO/IEC 23008-12/24) UltraHDR stream + * configurations that this camera device supports + * (i.e. format, width, height, output/input stream) for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Refer to android.heic.availableHeicStreamConfigurations for details.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final Key<android.hardware.camera2.params.StreamConfiguration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.heic.availableHeicUltraHdrStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class); + + /** + * <p>This lists the minimum frame duration for each + * format/size combination for HEIC UltraHDR output formats for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Refer to android.heic.availableHeicMinFrameDurations for details.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + * <p>This lists the maximum stall duration for each + * output format/size combination for HEIC UltraHDR streams for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Refer to android.heic.availableHeicStallDurations for details.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** * <p>The direction of the camera faces relative to the vehicle body frame and the * passenger seats.</p> * <p>This enum defines the lens facing characteristic of the cameras on the automotive diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index ef7f3f8ab58b..e22c263e893d 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -1385,6 +1385,9 @@ public class CameraMetadataNative implements Parcelable { /*jpegRconfiguration*/ null, /*jpegRminduration*/ null, /*jpegRstallduration*/ null, + /*heicUltraHDRconfiguration*/ null, + /*heicUltraHDRminduration*/ null, + /*heicUltraHDRstallduration*/ null, /*highspeedvideoconfigurations*/ null, /*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]); break; @@ -1402,6 +1405,9 @@ public class CameraMetadataNative implements Parcelable { /*jpegRconfiguration*/ null, /*jpegRminduration*/ null, /*jpegRstallduration*/ null, + /*heicUltraHDRconfiguration*/ null, + /*heicUltraHDRminduration*/ null, + /*heicUltraHDRstallduration*/ null, highSpeedVideoConfigurations, /*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]); break; @@ -1419,6 +1425,9 @@ public class CameraMetadataNative implements Parcelable { /*jpegRconfiguration*/ null, /*jpegRminduration*/ null, /*jpegRstallduration*/ null, + /*heicUltraHDRcconfiguration*/ null, + /*heicUltraHDRminduration*/ null, + /*heicUltraHDRstallduration*/ null, /*highSpeedVideoConfigurations*/ null, inputOutputFormatsMap, listHighResolution, supportsPrivate[i]); break; @@ -1436,6 +1445,9 @@ public class CameraMetadataNative implements Parcelable { /*jpegRconfiguration*/ null, /*jpegRminduration*/ null, /*jpegRstallduration*/ null, + /*heicUltraHDRcconfiguration*/ null, + /*heicUltraHDRminduration*/ null, + /*heicUltraHDRstallduration*/ null, /*highSpeedVideoConfigurations*/ null, /*inputOutputFormatsMap*/ null, listHighResolution, supportsPrivate[i]); } @@ -1607,6 +1619,17 @@ public class CameraMetadataNative implements Parcelable { CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS); StreamConfigurationDuration[] heicStallDurations = getBase( CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS); + StreamConfiguration[] heicUltraHDRConfigurations = null; + StreamConfigurationDuration[] heicUltraHDRMinFrameDurations = null; + StreamConfigurationDuration[] heicUltraHDRStallDurations = null; + if (Flags.cameraHeifGainmap()) { + heicUltraHDRConfigurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS); + heicUltraHDRMinFrameDurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS); + heicUltraHDRStallDurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS); + } StreamConfiguration[] jpegRConfigurations = getBase( CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS); StreamConfigurationDuration[] jpegRMinFrameDurations = getBase( @@ -1625,7 +1648,8 @@ public class CameraMetadataNative implements Parcelable { dynamicDepthStallDurations, heicConfigurations, heicMinFrameDurations, heicStallDurations, jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations, - highSpeedVideoConfigurations, inputOutputFormatsMap, + heicUltraHDRConfigurations, heicUltraHDRMinFrameDurations, + heicUltraHDRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution); } @@ -1662,6 +1686,17 @@ public class CameraMetadataNative implements Parcelable { CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION); StreamConfigurationDuration[] heicStallDurations = getBase( CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS_MAXIMUM_RESOLUTION); + StreamConfiguration[] heicUltraHDRConfigurations = null; + StreamConfigurationDuration[] heicUltraHDRMinFrameDurations = null; + StreamConfigurationDuration[] heicUltraHDRStallDurations = null; + if (Flags.cameraHeifGainmap()) { + heicUltraHDRConfigurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION); + heicUltraHDRMinFrameDurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION); + heicUltraHDRStallDurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS_MAXIMUM_RESOLUTION); + } StreamConfiguration[] jpegRConfigurations = getBase( CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION); StreamConfigurationDuration[] jpegRMinFrameDurations = getBase( @@ -1681,7 +1716,8 @@ public class CameraMetadataNative implements Parcelable { dynamicDepthStallDurations, heicConfigurations, heicMinFrameDurations, heicStallDurations, jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations, - highSpeedVideoConfigurations, inputOutputFormatsMap, + heicUltraHDRConfigurations, heicUltraHDRMinFrameDurations, + heicUltraHDRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution, false); } diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index e3dbb2bbbf90..ec028bf6dcbb 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkArrayElementsNotNull; import android.graphics.ImageFormat; import android.graphics.PixelFormat; +import android.hardware.DataSpace; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraMetadata; @@ -31,6 +32,8 @@ import android.util.Size; import android.util.SparseIntArray; import android.view.Surface; +import com.android.internal.camera.flags.Flags; + import java.util.Arrays; import java.util.HashMap; import java.util.Objects; @@ -100,6 +103,12 @@ public final class StreamConfigurationMap { * {@link StreamConfigurationDuration} * @param jpegRStallDurations a non-{@code null} array of Jpeg/R * {@link StreamConfigurationDuration} + * @param heicUltraHDRConfigurations a non-{@code null} array of Heic UltraHDR + * {@link StreamConfiguration} + * @param heicUltraHDRMinFrameDurations a non-{@code null} array of Heic UltraHDR + * {@link StreamConfigurationDuration} + * @param heicUltraHDRStallDurations a non-{@code null} array of Heic UltraHDR + * {@link StreamConfigurationDuration} * @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if * camera device does not support high speed video recording * @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE @@ -125,6 +134,9 @@ public final class StreamConfigurationMap { StreamConfiguration[] jpegRConfigurations, StreamConfigurationDuration[] jpegRMinFrameDurations, StreamConfigurationDuration[] jpegRStallDurations, + StreamConfiguration[] heicUltraHDRConfigurations, + StreamConfigurationDuration[] heicUltraHDRMinFrameDurations, + StreamConfigurationDuration[] heicUltraHDRStallDurations, HighSpeedVideoConfiguration[] highSpeedVideoConfigurations, ReprocessFormatsMap inputOutputFormatsMap, boolean listHighResolution) { @@ -134,8 +146,9 @@ public final class StreamConfigurationMap { dynamicDepthStallDurations, heicConfigurations, heicMinFrameDurations, heicStallDurations, jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations, - highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution, - /*enforceImplementationDefined*/ true); + heicUltraHDRConfigurations, heicUltraHDRMinFrameDurations, + heicUltraHDRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap, + listHighResolution, /*enforceImplementationDefined*/ true); } /** @@ -168,6 +181,12 @@ public final class StreamConfigurationMap { * {@link StreamConfigurationDuration} * @param jpegRStallDurations a non-{@code null} array of Jpeg/R * {@link StreamConfigurationDuration} + * @param heicUltraHDRConfigurations an array of Heic UltraHDR + * {@link StreamConfiguration}, {@code null} if camera doesn't support the format + * @param heicUltraHDRMinFrameDurations an array of Heic UltraHDR + * {@link StreamConfigurationDuration}, {@code null} if camera doesn't support the format + * @param heicUltraHDRStallDurations an array of Heic UltraHDR + * {@link StreamConfigurationDuration}, {@code null} if camera doesn't support the format * @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if * camera device does not support high speed video recording * @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE @@ -195,6 +214,9 @@ public final class StreamConfigurationMap { StreamConfiguration[] jpegRConfigurations, StreamConfigurationDuration[] jpegRMinFrameDurations, StreamConfigurationDuration[] jpegRStallDurations, + StreamConfiguration[] heicUltraHDRConfigurations, + StreamConfigurationDuration[] heicUltraHDRMinFrameDurations, + StreamConfigurationDuration[] heicUltraHDRStallDurations, HighSpeedVideoConfiguration[] highSpeedVideoConfigurations, ReprocessFormatsMap inputOutputFormatsMap, boolean listHighResolution, @@ -259,6 +281,18 @@ public final class StreamConfigurationMap { "heicStallDurations"); } + if (heicUltraHDRConfigurations == null || (!Flags.cameraHeifGainmap())) { + mHeicUltraHDRConfigurations = new StreamConfiguration[0]; + mHeicUltraHDRMinFrameDurations = new StreamConfigurationDuration[0]; + mHeicUltraHDRStallDurations = new StreamConfigurationDuration[0]; + } else { + mHeicUltraHDRConfigurations = checkArrayElementsNotNull(heicUltraHDRConfigurations, + "heicUltraHDRConfigurations"); + mHeicUltraHDRMinFrameDurations = checkArrayElementsNotNull( + heicUltraHDRMinFrameDurations, "heicUltraHDRMinFrameDurations"); + mHeicUltraHDRStallDurations = checkArrayElementsNotNull(heicUltraHDRStallDurations, + "heicUltraHDRStallDurations"); + } if (jpegRConfigurations == null) { mJpegRConfigurations = new StreamConfiguration[0]; @@ -336,6 +370,19 @@ public final class StreamConfigurationMap { mHeicOutputFormats.get(config.getFormat()) + 1); } + if (Flags.cameraHeifGainmap()) { + // For each Heic UlrtaHDR format, track how many sizes there are available to configure + for (StreamConfiguration config : mHeicUltraHDRConfigurations) { + if (!config.isOutput()) { + // Ignoring input Heic UltraHDR configs + continue; + } + + mHeicUltraHDROutputFormats.put(config.getFormat(), + mHeicUltraHDROutputFormats.get(config.getFormat()) + 1); + } + } + // For each Jpeg/R format, track how many sizes there are available to configure for (StreamConfiguration config : mJpegRConfigurations) { if (!config.isOutput()) { @@ -483,6 +530,11 @@ public final class StreamConfigurationMap { int internalFormat = imageFormatToInternal(format); int dataspace = imageFormatToDataspace(format); + if (Flags.cameraHeifGainmap()) { + if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) { + return mHeicUltraHDROutputFormats.indexOfKey(internalFormat) >= 0; + } + } if (dataspace == HAL_DATASPACE_DEPTH) { return mDepthOutputFormats.indexOfKey(internalFormat) >= 0; } else if (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) { @@ -607,6 +659,11 @@ public final class StreamConfigurationMap { surfaceDataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations : surfaceDataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations : mConfigurations; + if (Flags.cameraHeifGainmap()) { + if (surfaceDataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) { + configs = mHeicUltraHDRConfigurations; + } + } for (StreamConfiguration config : configs) { if (config.getFormat() == surfaceFormat && config.isOutput()) { // Matching format, either need exact size match, or a flexible consumer @@ -646,6 +703,11 @@ public final class StreamConfigurationMap { dataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations : dataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations : mConfigurations; + if (Flags.cameraHeifGainmap()) { + if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR ) { + configs = mHeicUltraHDRConfigurations; + } + } for (StreamConfiguration config : configs) { if ((config.getFormat() == internalFormat) && config.isOutput() && config.getSize().equals(size)) { @@ -1176,6 +1238,10 @@ public final class StreamConfigurationMap { Arrays.equals(mHeicConfigurations, other.mHeicConfigurations) && Arrays.equals(mHeicMinFrameDurations, other.mHeicMinFrameDurations) && Arrays.equals(mHeicStallDurations, other.mHeicStallDurations) && + Arrays.equals(mHeicUltraHDRConfigurations, other.mHeicUltraHDRConfigurations) && + Arrays.equals(mHeicUltraHDRMinFrameDurations, + other.mHeicUltraHDRMinFrameDurations) && + Arrays.equals(mHeicUltraHDRStallDurations, other.mHeicUltraHDRStallDurations) && Arrays.equals(mJpegRConfigurations, other.mJpegRConfigurations) && Arrays.equals(mJpegRMinFrameDurations, other.mJpegRMinFrameDurations) && Arrays.equals(mJpegRStallDurations, other.mJpegRStallDurations) && @@ -1197,8 +1263,9 @@ public final class StreamConfigurationMap { mDynamicDepthConfigurations, mDynamicDepthMinFrameDurations, mDynamicDepthStallDurations, mHeicConfigurations, mHeicMinFrameDurations, mHeicStallDurations, - mJpegRConfigurations, mJpegRMinFrameDurations, mJpegRStallDurations, - mHighSpeedVideoConfigurations); + mHeicUltraHDRConfigurations, mHeicUltraHDRMinFrameDurations, + mHeicUltraHDRStallDurations, mJpegRConfigurations, mJpegRMinFrameDurations, + mJpegRStallDurations, mHighSpeedVideoConfigurations); } // Check that the argument is supported by #getOutputFormats or #getInputFormats @@ -1209,6 +1276,13 @@ public final class StreamConfigurationMap { int internalDataspace = imageFormatToDataspace(format); if (output) { + if (Flags.cameraHeifGainmap()) { + if (internalDataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) { + if (mHeicUltraHDROutputFormats.indexOfKey(internalFormat) >= 0) { + return format; + } + } + } if (internalDataspace == HAL_DATASPACE_DEPTH) { if (mDepthOutputFormats.indexOfKey(internalFormat) >= 0) { return format; @@ -1429,6 +1503,7 @@ public final class StreamConfigurationMap { * <li>ImageFormat.DEPTH_POINT_CLOUD => HAL_PIXEL_FORMAT_BLOB * <li>ImageFormat.DEPTH_JPEG => HAL_PIXEL_FORMAT_BLOB * <li>ImageFormat.HEIC => HAL_PIXEL_FORMAT_BLOB + * <li>ImageFormat.HEIC_ULTRAHDR => HAL_PIXEL_FORMAT_BLOB * <li>ImageFormat.JPEG_R => HAL_PIXEL_FORMAT_BLOB * <li>ImageFormat.DEPTH16 => HAL_PIXEL_FORMAT_Y16 * </ul> @@ -1451,6 +1526,11 @@ public final class StreamConfigurationMap { * if {@code format} was {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED} */ static int imageFormatToInternal(int format) { + if (Flags.cameraHeifGainmap()) { + if (format == ImageFormat.HEIC_ULTRAHDR) { + return HAL_PIXEL_FORMAT_BLOB; + } + } switch (format) { case ImageFormat.JPEG: case ImageFormat.DEPTH_POINT_CLOUD: @@ -1480,6 +1560,7 @@ public final class StreamConfigurationMap { * <li>ImageFormat.DEPTH16 => HAL_DATASPACE_DEPTH * <li>ImageFormat.DEPTH_JPEG => HAL_DATASPACE_DYNAMIC_DEPTH * <li>ImageFormat.HEIC => HAL_DATASPACE_HEIF + * <li>ImageFormat.HEIC_ULTRAHDR => DATASPACE_HEIF_ULTRAHDR * <li>ImageFormat.JPEG_R => HAL_DATASPACE_JPEG_R * <li>ImageFormat.YUV_420_888 => HAL_DATASPACE_JFIF * <li>ImageFormat.RAW_SENSOR => HAL_DATASPACE_ARBITRARY @@ -1508,6 +1589,11 @@ public final class StreamConfigurationMap { * if {@code format} was {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED} */ static int imageFormatToDataspace(int format) { + if (Flags.cameraHeifGainmap()) { + if (format == ImageFormat.HEIC_ULTRAHDR) { + return DataSpace.DATASPACE_HEIF_ULTRAHDR; + } + } switch (format) { case ImageFormat.JPEG: return HAL_DATASPACE_V0_JFIF; @@ -1584,13 +1670,21 @@ public final class StreamConfigurationMap { dataspace == HAL_DATASPACE_JPEG_R ? mJpegROutputFormats : highRes ? mHighResOutputFormats : mOutputFormats; - + boolean isDataSpaceHeifUltraHDR = false; + if (Flags.cameraHeifGainmap()) { + if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) { + formatsMap = mHeicUltraHDROutputFormats; + isDataSpaceHeifUltraHDR = true; + } + } int sizesCount = formatsMap.get(format); if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH || dataspace == HAL_DATASPACE_JPEG_R || dataspace == HAL_DATASPACE_DYNAMIC_DEPTH || - dataspace == HAL_DATASPACE_HEIF)) && sizesCount == 0) || + dataspace == HAL_DATASPACE_HEIF || + isDataSpaceHeifUltraHDR)) && sizesCount == 0) || (output && (dataspace != HAL_DATASPACE_DEPTH && dataspace != HAL_DATASPACE_JPEG_R && dataspace != HAL_DATASPACE_DYNAMIC_DEPTH && + !isDataSpaceHeifUltraHDR && dataspace != HAL_DATASPACE_HEIF) && mAllOutputFormats.get(format) == 0)) { return null; @@ -1604,12 +1698,14 @@ public final class StreamConfigurationMap { (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations : (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations : + (isDataSpaceHeifUltraHDR) ? mHeicUltraHDRConfigurations : mConfigurations; StreamConfigurationDuration[] minFrameDurations = (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations : (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthMinFrameDurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations : (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations : + (isDataSpaceHeifUltraHDR) ? mHeicUltraHDRMinFrameDurations : mMinFrameDurations; for (StreamConfiguration config : configurations) { @@ -1639,7 +1735,8 @@ public final class StreamConfigurationMap { // Dynamic depth streams can have both fast and also high res modes. if ((sizeIndex != sizesCount) && (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH || - dataspace == HAL_DATASPACE_HEIF) || (dataspace == HAL_DATASPACE_JPEG_R)) { + dataspace == HAL_DATASPACE_HEIF) || (dataspace == HAL_DATASPACE_JPEG_R) || + isDataSpaceHeifUltraHDR) { if (sizeIndex > sizesCount) { throw new AssertionError( @@ -1682,6 +1779,11 @@ public final class StreamConfigurationMap { if (mHeicOutputFormats.size() > 0) { formats[i++] = ImageFormat.HEIC; } + if (Flags.cameraHeifGainmap()) { + if (mHeicUltraHDROutputFormats.size() > 0) { + formats[i++] = ImageFormat.HEIC_ULTRAHDR; + } + } if (mJpegROutputFormats.size() > 0) { formats[i++] = ImageFormat.JPEG_R; } @@ -1725,12 +1827,19 @@ public final class StreamConfigurationMap { * @see #DURATION_STALL * */ private StreamConfigurationDuration[] getDurations(int duration, int dataspace) { + boolean isDataSpaceHeifUltraHDR = false; + if (Flags.cameraHeifGainmap()) { + if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) { + isDataSpaceHeifUltraHDR = true; + } + } switch (duration) { case DURATION_MIN_FRAME: return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations : (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthMinFrameDurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations : + isDataSpaceHeifUltraHDR ? mHeicUltraHDRMinFrameDurations : (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations : mMinFrameDurations; @@ -1738,6 +1847,7 @@ public final class StreamConfigurationMap { return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthStallDurations : (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthStallDurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicStallDurations : + isDataSpaceHeifUltraHDR ? mHeicUltraHDRStallDurations : (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRStallDurations : mStallDurations; default: @@ -1754,6 +1864,7 @@ public final class StreamConfigurationMap { size += mDynamicDepthOutputFormats.size(); size += mHeicOutputFormats.size(); size += mJpegROutputFormats.size(); + size += mHeicUltraHDROutputFormats.size(); } return size; @@ -1774,11 +1885,18 @@ public final class StreamConfigurationMap { } private boolean isSupportedInternalConfiguration(int format, int dataspace, Size size) { + boolean isDataSpaceHeifUltraHDR = false; + if (Flags.cameraHeifGainmap()) { + if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) { + isDataSpaceHeifUltraHDR = true; + } + } StreamConfiguration[] configurations = (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations : (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations : (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations : + isDataSpaceHeifUltraHDR ? mHeicUltraHDRConfigurations : mConfigurations; for (int i = 0; i < configurations.length; i++) { @@ -1954,6 +2072,11 @@ public final class StreamConfigurationMap { * @hide */ public static String formatToString(int format) { + if (Flags.cameraHeifGainmap()) { + if (format == ImageFormat.HEIC_ULTRAHDR) { + return "HEIC_ULTRAHDR"; + } + } switch (format) { case ImageFormat.YV12: return "YV12"; @@ -2078,6 +2201,10 @@ public final class StreamConfigurationMap { private final StreamConfigurationDuration[] mHeicMinFrameDurations; private final StreamConfigurationDuration[] mHeicStallDurations; + private final StreamConfiguration[] mHeicUltraHDRConfigurations; + private final StreamConfigurationDuration[] mHeicUltraHDRMinFrameDurations; + private final StreamConfigurationDuration[] mHeicUltraHDRStallDurations; + private final StreamConfiguration[] mJpegRConfigurations; private final StreamConfigurationDuration[] mJpegRMinFrameDurations; private final StreamConfigurationDuration[] mJpegRStallDurations; @@ -2103,6 +2230,8 @@ public final class StreamConfigurationMap { private final SparseIntArray mDynamicDepthOutputFormats = new SparseIntArray(); /** internal format -> num heic output sizes mapping, for HAL_DATASPACE_HEIF */ private final SparseIntArray mHeicOutputFormats = new SparseIntArray(); + /** internal format -> num heic output sizes mapping, for DATASPACE_HEIF_GAINMAP */ + private final SparseIntArray mHeicUltraHDROutputFormats = new SparseIntArray(); /** internal format -> num Jpeg/R output sizes mapping, for HAL_DATASPACE_JPEG_R */ private final SparseIntArray mJpegROutputFormats = new SparseIntArray(); diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 3c6841cd8aeb..56307ae53a0c 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -1432,6 +1432,13 @@ public final class DisplayManagerGlobal { mExecutor.execute(mCallback::onStopped); } } + + @Override // Binder call + public void onRequestedBrightnessChanged(float brightness) { + if (mCallback != null) { + mExecutor.execute(() -> mCallback.onRequestedBrightnessChanged(brightness)); + } + } } /** diff --git a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl index c3490d177be2..9cc0364f2729 100644 --- a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl +++ b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl @@ -38,4 +38,9 @@ oneway interface IVirtualDisplayCallback { * of the application to release() the virtual display. */ void onStopped(); + + /** + * Called when the virtual display's requested brightness has changed. + */ + void onRequestedBrightnessChanged(float brightness); } diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java index 32b640583734..3b573ea98c27 100644 --- a/core/java/android/hardware/display/VirtualDisplay.java +++ b/core/java/android/hardware/display/VirtualDisplay.java @@ -16,6 +16,8 @@ package android.hardware.display; import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.SystemApi; import android.view.Display; import android.view.Surface; @@ -164,5 +166,25 @@ public final class VirtualDisplay { * of the application to release() the virtual display. */ public void onStopped() { } + + /** + * Called when the requested brightness of the display has changed. + * + * <p>The system may adjust the display's brightness based on user or app activity. This + * callback will only be invoked if the display has an explicitly specified default + * brightness value.</p> + * + * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of + * {@code 1.0} indicates the maximum supported brightness.</p> + * + * @see android.view.View#setKeepScreenOn(boolean) + * @see android.view.WindowManager.LayoutParams#screenBrightness + * @see VirtualDisplayConfig.Builder#setDefaultBrightness(float) + * @hide + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @SystemApi + public void onRequestedBrightnessChanged( + @FloatRange(from = 0.0f, to = 1.0f) float brightness) {} } } diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java index 49944c76eb99..57d9d28a9d47 100644 --- a/core/java/android/hardware/display/VirtualDisplayConfig.java +++ b/core/java/android/hardware/display/VirtualDisplayConfig.java @@ -29,6 +29,7 @@ import android.media.projection.MediaProjection; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; +import android.os.PowerManager; import android.util.ArraySet; import android.view.Display; import android.view.DisplayCutout; @@ -61,6 +62,7 @@ public final class VirtualDisplayConfig implements Parcelable { private final boolean mIsHomeSupported; private final DisplayCutout mDisplayCutout; private final boolean mIgnoreActivitySizeRestrictions; + private final float mDefaultBrightness; private VirtualDisplayConfig( @NonNull String name, @@ -76,7 +78,8 @@ public final class VirtualDisplayConfig implements Parcelable { float requestedRefreshRate, boolean isHomeSupported, @Nullable DisplayCutout displayCutout, - boolean ignoreActivitySizeRestrictions) { + boolean ignoreActivitySizeRestrictions, + @FloatRange(from = 0.0f, to = 1.0f) float defaultBrightness) { mName = name; mWidth = width; mHeight = height; @@ -91,6 +94,7 @@ public final class VirtualDisplayConfig implements Parcelable { mIsHomeSupported = isHomeSupported; mDisplayCutout = displayCutout; mIgnoreActivitySizeRestrictions = ignoreActivitySizeRestrictions; + mDefaultBrightness = defaultBrightness; } /** @@ -157,6 +161,22 @@ public final class VirtualDisplayConfig implements Parcelable { } /** + * Returns the default brightness of the display. + * + * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of {@code 1.0} + * indicates the maximum supported brightness.</p> + * + * @see Builder#setDefaultBrightness(float) + * @hide + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @SystemApi + public @FloatRange(from = 0.0f, to = 1.0f) float getDefaultBrightness() { + return mDefaultBrightness; + } + + + /** * Returns the unique identifier for the display. Shouldn't be displayed to the user. * @hide */ @@ -245,6 +265,7 @@ public final class VirtualDisplayConfig implements Parcelable { dest.writeBoolean(mIsHomeSupported); DisplayCutout.ParcelableWrapper.writeCutoutToParcel(mDisplayCutout, dest, flags); dest.writeBoolean(mIgnoreActivitySizeRestrictions); + dest.writeFloat(mDefaultBrightness); } @Override @@ -272,7 +293,8 @@ public final class VirtualDisplayConfig implements Parcelable { && mRequestedRefreshRate == that.mRequestedRefreshRate && mIsHomeSupported == that.mIsHomeSupported && mIgnoreActivitySizeRestrictions == that.mIgnoreActivitySizeRestrictions - && Objects.equals(mDisplayCutout, that.mDisplayCutout); + && Objects.equals(mDisplayCutout, that.mDisplayCutout) + && mDefaultBrightness == that.mDefaultBrightness; } @Override @@ -281,7 +303,7 @@ public final class VirtualDisplayConfig implements Parcelable { mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId, mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories, mRequestedRefreshRate, mIsHomeSupported, mDisplayCutout, - mIgnoreActivitySizeRestrictions); + mIgnoreActivitySizeRestrictions, mDefaultBrightness); return hashCode; } @@ -303,6 +325,7 @@ public final class VirtualDisplayConfig implements Parcelable { + " mIsHomeSupported=" + mIsHomeSupported + " mDisplayCutout=" + mDisplayCutout + " mIgnoreActivitySizeRestrictions=" + mIgnoreActivitySizeRestrictions + + " mDefaultBrightness=" + mDefaultBrightness + ")"; } @@ -321,6 +344,7 @@ public final class VirtualDisplayConfig implements Parcelable { mIsHomeSupported = in.readBoolean(); mDisplayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(in); mIgnoreActivitySizeRestrictions = in.readBoolean(); + mDefaultBrightness = in.readFloat(); } @NonNull @@ -355,6 +379,7 @@ public final class VirtualDisplayConfig implements Parcelable { private boolean mIsHomeSupported = false; private DisplayCutout mDisplayCutout = null; private boolean mIgnoreActivitySizeRestrictions = false; + private float mDefaultBrightness = 0.0f; /** * Creates a new Builder. @@ -547,6 +572,35 @@ public final class VirtualDisplayConfig implements Parcelable { } /** + * Sets the default brightness of the display. + * + * <p>The system will use this brightness value whenever the display should be bright, i.e. + * it is powered on and not dimmed due to user activity or app activity.</p> + * + * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of + * {@code 1.0} indicates the maximum supported brightness.</p> + * + * <p>If unset, defaults to {@code 0.0}</p> + * + * @see android.view.View#setKeepScreenOn(boolean) + * @see Builder#setDefaultBrightness(float) + * @see VirtualDisplay.Callback#onRequestedBrightnessChanged(float) + * @hide + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @SystemApi + @NonNull + public Builder setDefaultBrightness(@FloatRange(from = 0.0f, to = 1.0f) float brightness) { + if (brightness < PowerManager.BRIGHTNESS_MIN + || brightness > PowerManager.BRIGHTNESS_MAX) { + throw new IllegalArgumentException( + "Virtual display default brightness must be in range [0.0, 1.0]"); + } + mDefaultBrightness = brightness; + return this; + } + + /** * Builds the {@link VirtualDisplayConfig} instance. */ @NonNull @@ -565,7 +619,8 @@ public final class VirtualDisplayConfig implements Parcelable { mRequestedRefreshRate, mIsHomeSupported, mDisplayCutout, - mIgnoreActivitySizeRestrictions); + mIgnoreActivitySizeRestrictions, + mDefaultBrightness); } } } diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java index c4566981d3b5..96f6ad117035 100644 --- a/core/java/android/hardware/input/InputSettings.java +++ b/core/java/android/hardware/input/InputSettings.java @@ -30,6 +30,7 @@ import static com.android.hardware.input.Flags.mouseSwapPrimaryButton; import static com.android.hardware.input.Flags.touchpadTapDragging; import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut; import static com.android.hardware.input.Flags.touchpadVisualizer; +import static com.android.hardware.input.Flags.useKeyGestureEventHandler; import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS; import static com.android.input.flags.Flags.enableInputFilterRustImpl; import static com.android.input.flags.Flags.keyboardRepeatKeys; @@ -386,7 +387,7 @@ public class InputSettings { * @hide */ public static boolean isTouchpadThreeFingerTapShortcutFeatureFlagEnabled() { - return enableCustomizableInputGestures() && touchpadThreeFingerTapShortcut(); + return isCustomizableInputGesturesFeatureFlagEnabled() && touchpadThreeFingerTapShortcut(); } /** @@ -1132,4 +1133,18 @@ public class InputSettings { Settings.Secure.KEY_REPEAT_DELAY_MS, delayTimeMillis, UserHandle.USER_CURRENT); } + + /** + * Whether "Customizable key gestures" feature flag is enabled. + * + * <p> + * ‘Customizable key gestures’ is a feature which allows users to customize key based + * shortcuts on the physical keyboard. + * </p> + * + * @hide + */ + public static boolean isCustomizableInputGesturesFeatureFlagEnabled() { + return enableCustomizableInputGestures() && useKeyGestureEventHandler(); + } } diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index c6fd0ee3c80d..85cf9491287c 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -1750,8 +1750,8 @@ public class SoundTrigger { * internals, typically during enrollment. * @return the same Builder instance. */ - public @NonNull Builder setData(@Nullable byte[] data) { - mData = data; + public @NonNull Builder setData(@NonNull byte[] data) { + mData = requireNonNull(data, "Data must not be null"); return this; } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 8c3f0ef08039..ae8366817f2b 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -55,6 +55,7 @@ import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECT import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER; import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED; import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING; +import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API; import static android.view.inputmethod.Flags.ctrlShiftShortcut; import static android.view.inputmethod.Flags.predictiveBackIme; @@ -4392,6 +4393,39 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * Called when the requested visibility of a custom IME Switcher button changes. + * + * <p>When the system provides an IME navigation bar, it may decide to show an IME Switcher + * button inside this bar. However, the IME can request hiding the bar provided by the system + * with {@code getWindowInsetsController().hide(captionBar())} (the IME navigation bar provides + * {@link Type#captionBar() captionBar} insets to the IME window). If the request is successful, + * then it becomes the IME's responsibility to provide a custom IME Switcher button in its + * input view, with equivalent functionality.</p> + * + * <p>This custom button is only requested to be visible when the system provides the IME + * navigation bar, both the bar and the IME Switcher button inside it should be visible, + * but the IME successfully requested to hide the bar. This does not depend on the current + * visibility of the IME. It could be called with {@code true} while the IME is hidden, in + * which case the IME should prepare to show the button as soon as the IME itself is shown.</p> + * + * <p>This is only called when the requested visibility changes. The default value is + * {@code false} and as such, this will not be called initially if the resulting value is + * {@code false}.</p> + * + * <p>This can be called at any time after {@link #onCreate}, even if the IME is not currently + * visible. However, this is not guaranteed to be called before the IME is shown, as it depends + * on when the IME requested hiding the IME navigation bar. If the request is sent during + * the showing flow (e.g. during {@link #onStartInputView}), this will be called shortly after + * {@link #onWindowShown}, but before the first IME frame is drawn.</p> + * + * @param visible whether the button is requested visible or not. + */ + @FlaggedApi(FLAG_IME_SWITCHER_REVAMP_API) + public void onCustomImeSwitcherButtonRequestedVisible(boolean visible) { + // Intentionally empty + } + + /** * Called when the IME switch button was clicked from the client. Depending on the number of * enabled IME subtypes, this will either switch to the next IME/subtype, or show the input * method picker dialog. diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index b08454dd7f8f..38be8d9f772d 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -41,6 +41,7 @@ import android.view.WindowInsets; import android.view.WindowInsetsController.Appearance; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import android.view.inputmethod.Flags; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; @@ -178,6 +179,9 @@ final class NavigationBarController { private boolean mDrawLegacyNavigationBarBackground; + /** Whether a custom IME Switcher button should be visible. */ + private boolean mCustomImeSwitcherVisible; + private final Rect mTempRect = new Rect(); private final int[] mTempPos = new int[2]; @@ -265,6 +269,7 @@ final class NavigationBarController { // IME navigation bar. boolean visible = insets.isVisible(captionBar()); mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE); + checkCustomImeSwitcherVisibility(); } return view.onApplyWindowInsets(insets); }); @@ -491,6 +496,8 @@ final class NavigationBarController { mShouldShowImeSwitcherWhenImeIsShown; mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown; + checkCustomImeSwitcherVisibility(); + mService.mWindow.getWindow().getDecorView().getWindowInsetsController() .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight(imeDrawsImeNavBar)); @@ -616,12 +623,33 @@ final class NavigationBarController { && mNavigationBarFrame.getVisibility() == View.VISIBLE; } + /** + * Checks if a custom IME Switcher button should be visible, and notifies the IME when this + * state changes. This can only be {@code true} if three conditions are met: + * + * <li>The IME should draw the IME navigation bar.</li> + * <li>The IME Switcher button should be visible when the IME is visible.</li> + * <li>The IME navigation bar should be visible, but was requested hidden by the IME.</li> + */ + private void checkCustomImeSwitcherVisibility() { + if (!Flags.imeSwitcherRevampApi()) { + return; + } + final boolean visible = mImeDrawsImeNavBar && mShouldShowImeSwitcherWhenImeIsShown + && mNavigationBarFrame != null && !isShown(); + if (visible != mCustomImeSwitcherVisible) { + mCustomImeSwitcherVisible = visible; + mService.onCustomImeSwitcherButtonRequestedVisible(mCustomImeSwitcherVisible); + } + } + @Override public String toDebugString() { return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar + " mNavigationBarFrame=" + mNavigationBarFrame + " mShouldShowImeSwitcherWhenImeIsShown=" + mShouldShowImeSwitcherWhenImeIsShown + + " mCustomImeSwitcherVisible=" + mCustomImeSwitcherVisible + " mAppearance=0x" + Integer.toHexString(mAppearance) + " mDarkIntensity=" + mDarkIntensity + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 66f4198ad31c..6cfbf4ebf661 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -93,9 +93,7 @@ public final class MessageQueue { * system processes and provides a higher level of concurrency and higher enqueue throughput * than the legacy implementation. */ - private static boolean sUseConcurrent; - - private static boolean sUseConcurrentInitialized = false; + private boolean mUseConcurrent; @RavenwoodRedirect private native static long nativeInit(); @@ -112,10 +110,7 @@ public final class MessageQueue { private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events); MessageQueue(boolean quitAllowed) { - if (!sUseConcurrentInitialized) { - sUseConcurrent = UserHandle.isCore(Process.myUid()); - sUseConcurrentInitialized = true; - } + mUseConcurrent = UserHandle.isCore(Process.myUid()); mQuitAllowed = quitAllowed; mPtr = nativeInit(); } @@ -158,7 +153,7 @@ public final class MessageQueue { * @return True if the looper is idle. */ public boolean isIdle() { - if (sUseConcurrent) { + if (mUseConcurrent) { final long now = SystemClock.uptimeMillis(); if (stackHasMessages(null, 0, null, null, now, mMatchDeliverableMessages, false)) { @@ -208,7 +203,7 @@ public final class MessageQueue { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } - if (sUseConcurrent) { + if (mUseConcurrent) { synchronized (mIdleHandlersLock) { mIdleHandlers.add(handler); } @@ -229,7 +224,7 @@ public final class MessageQueue { * @param handler The IdleHandler to be removed. */ public void removeIdleHandler(@NonNull IdleHandler handler) { - if (sUseConcurrent) { + if (mUseConcurrent) { synchronized (mIdleHandlersLock) { mIdleHandlers.remove(handler); } @@ -252,7 +247,7 @@ public final class MessageQueue { * @hide */ public boolean isPolling() { - if (sUseConcurrent) { + if (mUseConcurrent) { // If the loop is quitting then it must not be idling. // We can assume mPtr != 0 when sQuitting is false. return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr); @@ -303,7 +298,7 @@ public final class MessageQueue { throw new IllegalArgumentException("listener must not be null"); } - if (sUseConcurrent) { + if (mUseConcurrent) { synchronized (mFileDescriptorRecordsLock) { updateOnFileDescriptorEventListenerLocked(fd, events, listener); } @@ -331,7 +326,7 @@ public final class MessageQueue { if (fd == null) { throw new IllegalArgumentException("fd must not be null"); } - if (sUseConcurrent) { + if (mUseConcurrent) { synchronized (mFileDescriptorRecordsLock) { updateOnFileDescriptorEventListenerLocked(fd, 0, null); } @@ -388,7 +383,7 @@ public final class MessageQueue { final int oldWatchedEvents; final OnFileDescriptorEventListener listener; final int seq; - if (sUseConcurrent) { + if (mUseConcurrent) { synchronized (mFileDescriptorRecordsLock) { record = mFileDescriptorRecords.get(fd); if (record == null) { @@ -708,7 +703,7 @@ public final class MessageQueue { @UnsupportedAppUsage Message next() { - if (sUseConcurrent) { + if (mUseConcurrent) { return nextConcurrent(); } @@ -834,7 +829,7 @@ public final class MessageQueue { throw new IllegalStateException("Main thread not allowed to quit."); } - if (sUseConcurrent) { + if (mUseConcurrent) { synchronized (mIdleHandlersLock) { if (sQuitting.compareAndSet(this, false, true)) { if (safe) { @@ -898,7 +893,7 @@ public final class MessageQueue { private int postSyncBarrier(long when) { // Enqueue a new sync barrier token. // We don't need to wake the queue because the purpose of a barrier is to stall it. - if (sUseConcurrent) { + if (mUseConcurrent) { final int token = mNextBarrierTokenAtomic.getAndIncrement(); // b/376573804: apps and tests may expect to be able to use reflection @@ -991,7 +986,7 @@ public final class MessageQueue { public void removeSyncBarrier(int token) { // Remove a sync barrier token from the queue. // If the queue is no longer stalled by a barrier then wake it. - if (sUseConcurrent) { + if (mUseConcurrent) { boolean removed; MessageNode first; final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token); @@ -1058,7 +1053,7 @@ public final class MessageQueue { throw new IllegalArgumentException("Message must have a target."); } - if (sUseConcurrent) { + if (mUseConcurrent) { if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } @@ -1187,7 +1182,7 @@ public final class MessageQueue { if (h == null) { return false; } - if (sUseConcurrent) { + if (mUseConcurrent) { return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, false); } @@ -1219,7 +1214,7 @@ public final class MessageQueue { if (h == null) { return false; } - if (sUseConcurrent) { + if (mUseConcurrent) { return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, false); @@ -1253,7 +1248,7 @@ public final class MessageQueue { if (h == null) { return false; } - if (sUseConcurrent) { + if (mUseConcurrent) { return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, false); } @@ -1285,7 +1280,7 @@ public final class MessageQueue { if (h == null) { return false; } - if (sUseConcurrent) { + if (mUseConcurrent) { return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false); } synchronized (this) { @@ -1304,7 +1299,7 @@ public final class MessageQueue { if (h == null) { return; } - if (sUseConcurrent) { + if (mUseConcurrent) { findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true); return; } @@ -1355,7 +1350,7 @@ public final class MessageQueue { return; } - if (sUseConcurrent) { + if (mUseConcurrent) { findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true); return; } @@ -1407,7 +1402,7 @@ public final class MessageQueue { return; } - if (sUseConcurrent) { + if (mUseConcurrent) { findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true); return; } @@ -1470,7 +1465,7 @@ public final class MessageQueue { return; } - if (sUseConcurrent) { + if (mUseConcurrent) { findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true); return; } @@ -1532,7 +1527,7 @@ public final class MessageQueue { return; } - if (sUseConcurrent) { + if (mUseConcurrent) { findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true); return; } @@ -1594,7 +1589,7 @@ public final class MessageQueue { return; } - if (sUseConcurrent) { + if (mUseConcurrent) { findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true); return; } @@ -1742,7 +1737,7 @@ public final class MessageQueue { @NeverCompile void dump(Printer pw, String prefix, Handler h) { - if (sUseConcurrent) { + if (mUseConcurrent) { long now = SystemClock.uptimeMillis(); int n = 0; @@ -1803,7 +1798,7 @@ public final class MessageQueue { @NeverCompile void dumpDebug(ProtoOutputStream proto, long fieldId) { - if (sUseConcurrent) { + if (mUseConcurrent) { final long messageQueueToken = proto.start(fieldId); StackNode node = (StackNode) sState.getVolatile(this); diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 6a4932211f27..9e0d0e195a96 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -53,6 +53,24 @@ flag { } flag { + name: "enhanced_confirmation_in_call_apis_enabled" + is_exported: true + is_fixed_read_only: true + namespace: "permissions" + description: "enable enhanced confirmation incall apis" + bug: "310220212" +} + +flag { + name: "unknown_call_package_install_blocking_enabled" + is_exported: true + is_fixed_read_only: true + namespace: "permissions" + description: "enable the blocking of certain app installs during an unknown call" + bug: "310220212" +} + +flag { name: "op_enable_mobile_data_by_user" is_exported: true namespace: "permissions" @@ -332,3 +350,38 @@ flag { description: "Enables ExtServices to leverage TextClassifier for OTP detection" bug: "351976749" } + +flag { + name: "health_connect_backup_restore_permission_enabled" + is_fixed_read_only: true + namespace: "health_connect" + description: "This flag protects the permission that is required to call Health Connect backup and restore apis" + bug: "376014879" # android_fr bug +} + +flag { + name: "enable_aiai_proxied_text_classifiers" + is_fixed_read_only: true + is_exported: true + namespace: "permissions" + description: "Enables the AiAi to utilize the default OTP text classifier that is also used by ExtServices" + bug: "377229653" +} + +flag { + name: "enable_sqlite_appops_accesses" + is_fixed_read_only: true + is_exported: true + namespace: "permissions" + description: "Enables SQlite for recording discrete and historical AppOp accesses" + bug: "377584611" +} + +flag { + name: "ranging_permission_enabled" + is_fixed_read_only: true + is_exported: true + namespace: "uwb" + description: "This fixed read-only flag is used to enable new ranging permission for all ranging use cases." + bug: "370977414" +} diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS index 0809de25b45c..ce79f5d0c669 100644 --- a/core/java/android/print/OWNERS +++ b/core/java/android/print/OWNERS @@ -2,3 +2,4 @@ anothermark@google.com kumarashishg@google.com +bmgordon@google.com diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS index 0809de25b45c..ce79f5d0c669 100644 --- a/core/java/android/printservice/OWNERS +++ b/core/java/android/printservice/OWNERS @@ -2,3 +2,4 @@ anothermark@google.com kumarashishg@google.com +bmgordon@google.com diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 2e660fc1f157..7d79fd3d44ea 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -1164,7 +1164,7 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { public boolean startRecognition(@RecognitionFlags int recognitionFlags) { if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")"); synchronized (mLock) { - return startRecognitionLocked(recognitionFlags, null /* data */) == STATUS_OK; + return startRecognitionLocked(recognitionFlags, /* data= */new byte[0]) == STATUS_OK; } } @@ -1496,8 +1496,8 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } @GuardedBy("mLock") - private int startRecognitionLocked(int recognitionFlags, - @Nullable byte[] data) { + @SuppressWarnings("FlaggedApi") // RecognitionConfig.Builder is available internally. + private int startRecognitionLocked(int recognitionFlags, @NonNull byte[] data) { if (DBG) { Slog.d(TAG, "startRecognition(" + recognitionFlags diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index e830d89d3116..02923eda308e 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -202,3 +202,10 @@ flag { description: "Deprecate the Paint#elegantTextHeight API and stick it to true" bug: "349519475" } + +flag { + name: "vertical_text_layout" + namespace: "text" + description: "Make Paint class work for vertical layout text." + bug: "355296926" +} diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index 8358b9a51adb..1dd9d46fdfb7 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -75,8 +75,7 @@ import java.net.UnknownHostException; @android.ravenwood.annotation.RavenwoodClassLoadHook( "com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook.onClassLoaded") // Uncomment the following annotation to switch to the Java substitution version. -//@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( -// "com.android.platform.test.ravenwood.nativesubstitution.Log_host") +@android.ravenwood.annotation.RavenwoodRedirectionClass("Log_host") public final class Log { /** @hide */ @IntDef({ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE}) @@ -250,6 +249,7 @@ public final class Log { * tag limit of concern after this API level. */ @FastNative + @android.ravenwood.annotation.RavenwoodRedirect public static native boolean isLoggable(@Nullable String tag, @Level int level); /** @@ -425,6 +425,7 @@ public final class Log { * @hide */ @UnsupportedAppUsage + @android.ravenwood.annotation.RavenwoodRedirect public static native int println_native(int bufID, int priority, String tag, String msg); /** @@ -452,6 +453,7 @@ public final class Log { * Return the maximum payload the log daemon accepts without truncation. * @return LOGGER_ENTRY_MAX_PAYLOAD. */ + @android.ravenwood.annotation.RavenwoodRedirect private static native int logger_entry_max_payload_native(); /** diff --git a/core/java/android/view/RoundScrollbarRenderer.java b/core/java/android/view/RoundScrollbarRenderer.java index 59c2598f00f0..5e1eadae0953 100644 --- a/core/java/android/view/RoundScrollbarRenderer.java +++ b/core/java/android/view/RoundScrollbarRenderer.java @@ -35,7 +35,9 @@ import android.view.flags.Flags; * @hide */ public class RoundScrollbarRenderer { - private static final String BLUECHIP_ENABLED_SYSPROP = "persist.cw_build.bluechip.enabled"; + /** @hide */ + public static final String BLUECHIP_ENABLED_SYSPROP = "persist.cw_build.bluechip.enabled"; + // The range of the scrollbar position represented as an angle in degrees. private static final float SCROLLBAR_ANGLE_RANGE = 28.8f; private static final float MAX_SCROLLBAR_ANGLE_SWIPE = 26.3f; // 90% diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index b8b22e283175..df54d310059f 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -312,6 +312,7 @@ public final class SurfaceControl implements Parcelable { private static native void nativeNotifyShutdown(); private static native void nativeSetLuts(long transactionObj, long nativeObject, float[] buffers, int[] slots, int[] dimensions, int[] sizes, int[] samplingKeys); + private static native void nativeEnableDebugLogCallPoints(long transactionObj); /** * Transforms that can be applied to buffers as they are displayed to a window. @@ -4605,7 +4606,6 @@ public final class SurfaceControl implements Parcelable { } /** - * TODO(b/366484871): To be removed once we have some logging in native * This is called when BlastBufferQueue.mergeWithNextTransaction() is called from java, and * for the purposes of logging that path. */ @@ -4616,6 +4616,7 @@ public final class SurfaceControl implements Parcelable { if (mCalls != null) { mCalls.clear(); } + nativeEnableDebugLogCallPoints(mNativeObject); } } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 5e5f33ef41f8..59a82bef1c92 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -857,8 +857,10 @@ public class AccessibilityNodeInfo implements Parcelable { * <p> * The data can be retrieved from the {@code Bundle} returned by {@link #getExtras()} using this * string as a key for {@link Bundle#getParcelableArray(String, Class)}. The - * {@link android.graphics.RectF} will be null for characters that either do not exist or are - * off the screen. + * {@link android.graphics.RectF} will be {@code null} for characters that either do not exist + * or are off the screen. + * <p> + * Note that character locations returned are modified by changes in display magnification. * * {@see #refreshWithExtraData(String, Bundle)} */ @@ -866,6 +868,36 @@ public class AccessibilityNodeInfo implements Parcelable { "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY"; /** + * Key used to request and locate extra data for text character location in + * window coordinates. This key requests that an array of + * {@link android.graphics.RectF}s be added to the extras. This request is made + * with {@link #refreshWithExtraData(String, Bundle)}. The arguments taken by + * this request are two integers: + * {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX} and + * {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH}. The starting index + * must be valid inside the CharSequence returned by {@link #getText()}, and + * the length must be positive. + * <p> + * Providers may advertise that they support text characters in window coordinates using + * {@link #setAvailableExtraData(List)}. Services may check if an implementation supports text + * characters in window coordinates with {@link #getAvailableExtraData()}. + * <p> + * The data can be retrieved from the {@code Bundle} returned by + * {@link #getExtras()} using this string as a key for + * {@link Bundle#getParcelableArray(String, Class)}. The + * {@link android.graphics.RectF} will be {@code null} for characters that either do + * not exist or are outside of the window bounds. + * <p> + * Note that character locations in window bounds are not modified by + * changes in display magnification. + * + * {@see #refreshWithExtraData(String, Bundle)} + */ + @FlaggedApi(Flags.FLAG_A11Y_CHARACTER_IN_WINDOW_API) + public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY = + "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY"; + + /** * Integer argument specifying the start index of the requested text location data. Must be * valid inside the CharSequence returned by {@link #getText()}. * @@ -5573,20 +5605,13 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("; maxTextLength: ").append(mMaxTextLength); builder.append("; stateDescription: ").append(mStateDescription); builder.append("; contentDescription: ").append(mContentDescription); - if (Flags.supplementalDescription()) { - builder.append("; supplementalDescription: ").append(mSupplementalDescription); - } builder.append("; tooltipText: ").append(mTooltipText); builder.append("; containerTitle: ").append(mContainerTitle); builder.append("; viewIdResName: ").append(mViewIdResourceName); builder.append("; uniqueId: ").append(mUniqueId); builder.append("; expandedState: ").append(getExpandedStateSymbolicName(mExpandedState)); - builder.append("; checkable: ").append(isCheckable()); builder.append("; checked: ").append(isChecked()); - if (Flags.a11yIsRequiredApi()) { - builder.append("; required: ").append(isFieldRequired()); - } builder.append("; focusable: ").append(isFocusable()); builder.append("; focused: ").append(isFocused()); builder.append("; selected: ").append(isSelected()); diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java index be91cfb9ebf8..a67ae7c96a54 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtype.java +++ b/core/java/android/view/inputmethod/InputMethodSubtype.java @@ -17,8 +17,10 @@ package android.view.inputmethod; import android.annotation.AnyThread; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringRes; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; @@ -87,8 +89,17 @@ public final class InputMethodSubtype implements Parcelable { private final boolean mIsAsciiCapable; private final int mSubtypeHashCode; private final int mSubtypeIconResId; + /** The subtype name resource identifier. */ private final int mSubtypeNameResId; + /** The untranslatable name of the subtype. */ + @NonNull private final CharSequence mSubtypeNameOverride; + /** The layout label string resource identifier. */ + @StringRes + private final int mLayoutLabelResId; + /** The non-localized layout label. */ + @NonNull + private final CharSequence mLayoutLabelNonLocalized; private final String mPkLanguageTag; private final String mPkLayoutType; private final int mSubtypeId; @@ -176,6 +187,7 @@ public final class InputMethodSubtype implements Parcelable { mSubtypeNameResId = subtypeNameResId; return this; } + /** The subtype name resource identifier. */ private int mSubtypeNameResId = 0; /** @@ -191,9 +203,56 @@ public final class InputMethodSubtype implements Parcelable { mSubtypeNameOverride = nameOverride; return this; } + /** The untranslatable name of the subtype. */ + @NonNull private CharSequence mSubtypeNameOverride = ""; /** + * Sets the layout label string resource identifier. + * + * @param layoutLabelResId the layout label string resource identifier. + * + * @see #getLayoutDisplayName + */ + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + @NonNull + public InputMethodSubtypeBuilder setLayoutLabelResource( + @StringRes int layoutLabelResId) { + if (!Flags.imeSwitcherRevampApi()) { + return this; + } + mLayoutLabelResId = layoutLabelResId; + return this; + } + /** The layout label string resource identifier. */ + @StringRes + private int mLayoutLabelResId = 0; + + /** + * Sets the non-localized layout label. This is used as the layout display name if the + * {@link #getLayoutLabelResource layoutLabelResource} is not set ({@code 0}). + * + * @param layoutLabelNonLocalized the non-localized layout label. + * + * @see #getLayoutDisplayName + */ + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + @NonNull + public InputMethodSubtypeBuilder setLayoutLabelNonLocalized( + @NonNull CharSequence layoutLabelNonLocalized) { + if (!Flags.imeSwitcherRevampApi()) { + return this; + } + Objects.requireNonNull(layoutLabelNonLocalized, + "layoutLabelNonLocalized cannot be null"); + mLayoutLabelNonLocalized = layoutLabelNonLocalized; + return this; + } + /** The non-localized layout label. */ + @NonNull + private CharSequence mLayoutLabelNonLocalized = ""; + + /** * Sets the physical keyboard hint information, such as language and layout. * * The system can use the hint information to automatically configure the physical keyboard @@ -350,6 +409,8 @@ public final class InputMethodSubtype implements Parcelable { private InputMethodSubtype(InputMethodSubtypeBuilder builder) { mSubtypeNameResId = builder.mSubtypeNameResId; mSubtypeNameOverride = builder.mSubtypeNameOverride; + mLayoutLabelResId = builder.mLayoutLabelResId; + mLayoutLabelNonLocalized = builder.mLayoutLabelNonLocalized; mPkLanguageTag = builder.mPkLanguageTag; mPkLayoutType = builder.mPkLayoutType; mSubtypeIconResId = builder.mSubtypeIconResId; @@ -376,6 +437,9 @@ public final class InputMethodSubtype implements Parcelable { mSubtypeNameResId = source.readInt(); CharSequence cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); mSubtypeNameOverride = cs != null ? cs : ""; + mLayoutLabelResId = source.readInt(); + cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + mLayoutLabelNonLocalized = cs != null ? cs : ""; s = source.readString8(); mPkLanguageTag = s != null ? s : ""; s = source.readString8(); @@ -412,6 +476,24 @@ public final class InputMethodSubtype implements Parcelable { } /** + * Returns the layout label string resource identifier. + */ + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + @StringRes + public int getLayoutLabelResource() { + return mLayoutLabelResId; + } + + /** + * Returns the non-localized layout label. + */ + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + @NonNull + public CharSequence getLayoutLabelNonLocalized() { + return mLayoutLabelNonLocalized; + } + + /** * Returns the physical keyboard BCP-47 language tag. * * @attr ref android.R.styleable#InputMethod_Subtype_physicalKeyboardHintLanguageTag @@ -643,9 +725,47 @@ public final class InputMethodSubtype implements Parcelable { try { return String.format(subtypeNameString, replacementString); } catch (IllegalFormatException e) { - Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e); + Slog.w(TAG, "Found illegal format in subtype name(" + subtypeName + "): " + e); + return ""; + } + } + + /** + * Returns the layout display name. + * + * <p>If {@code layoutLabelResource} is non-zero (specified through + * {@link InputMethodSubtypeBuilder#setLayoutLabelResource setLayoutLabelResource}), the + * text generated from that resource will be returned. The localized string resource of the + * label should be capitalized for inclusion in UI lists. + * + * <p>If {@code layoutLabelResource} is zero, the framework returns the non-localized + * layout label, if specified through + * {@link InputMethodSubtypeBuilder#setLayoutLabelNonLocalized setLayoutLabelNonLocalized}. + * + * @param context The context used for getting the + * {@link android.content.pm.PackageManager PackageManager}. + * @param imeAppInfo The {@link ApplicationInfo} of the input method. + * @return the layout display name. + */ + @NonNull + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + public CharSequence getLayoutDisplayName(@NonNull Context context, + @NonNull ApplicationInfo imeAppInfo) { + if (!Flags.imeSwitcherRevampApi()) { + return ""; + } + Objects.requireNonNull(context, "context cannot be null"); + Objects.requireNonNull(imeAppInfo, "imeAppInfo cannot be null"); + if (mLayoutLabelResId == 0) { + return mLayoutLabelNonLocalized; + } + + final CharSequence subtypeLayoutName = context.getPackageManager().getText( + imeAppInfo.packageName, mLayoutLabelResId, imeAppInfo); + if (TextUtils.isEmpty(subtypeLayoutName)) { return ""; } + return subtypeLayoutName; } @Nullable @@ -778,6 +898,8 @@ public final class InputMethodSubtype implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeInt(mSubtypeNameResId); TextUtils.writeToParcel(mSubtypeNameOverride, dest, parcelableFlags); + dest.writeInt(mLayoutLabelResId); + TextUtils.writeToParcel(mLayoutLabelNonLocalized, dest, parcelableFlags); dest.writeString8(mPkLanguageTag); dest.writeString8(mPkLayoutType); dest.writeInt(mSubtypeIconResId); @@ -794,6 +916,7 @@ public final class InputMethodSubtype implements Parcelable { void dump(@NonNull Printer pw, @NonNull String prefix) { pw.println(prefix + "mSubtypeNameOverride=" + mSubtypeNameOverride + + " mLayoutLabelNonLocalized=" + mLayoutLabelNonLocalized + " mPkLanguageTag=" + mPkLanguageTag + " mPkLayoutType=" + mPkLayoutType + " mSubtypeId=" + mSubtypeId diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index ef941da0e32d..d7750bd412a3 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -26,6 +26,9 @@ import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDER import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; +import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY; +import static android.view.accessibility.Flags.FLAG_A11Y_CHARACTER_IN_WINDOW_API; +import static android.view.accessibility.Flags.a11yCharacterInWindowApi; import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY; import static android.view.inputmethod.Flags.initiationWithoutInputConnection; @@ -492,6 +495,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** Accessibility action start id for "smart" actions. @hide */ static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000; + // Stable extra data keys supported by TextView. + private static final List<String> ACCESSIBILITY_EXTRA_DATA_KEYS = List.of( + EXTRA_DATA_RENDERING_INFO_KEY, + EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY + ); + + // Flagged and stable extra data keys supported by TextView. + @FlaggedApi(FLAG_A11Y_CHARACTER_IN_WINDOW_API) + private static final List<String> ACCESSIBILITY_EXTRA_DATA_KEYS_FLAGGED = List.of( + EXTRA_DATA_RENDERING_INFO_KEY, + EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, + EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY + ); + /** * @hide */ @@ -14207,10 +14224,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); - info.setAvailableExtraData(Arrays.asList( - EXTRA_DATA_RENDERING_INFO_KEY, - EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY - )); + if (a11yCharacterInWindowApi()) { + info.setAvailableExtraData(ACCESSIBILITY_EXTRA_DATA_KEYS_FLAGGED); + } else { + info.setAvailableExtraData(ACCESSIBILITY_EXTRA_DATA_KEYS); + } info.setTextSelectable(isTextSelectable() || isTextEditable()); } else { info.setAvailableExtraData(Arrays.asList( @@ -14275,7 +14293,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public void addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { - if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { + boolean isCharacterLocationKey = extraDataKey.equals( + EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY); + boolean isCharacterLocationInWindowKey = (a11yCharacterInWindowApi() && extraDataKey.equals( + EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY)); + if (arguments != null && (isCharacterLocationKey || isCharacterLocationInWindowKey)) { int positionInfoStartIndex = arguments.getInt( EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); int positionInfoLength = arguments.getInt( @@ -14297,7 +14319,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener RectF bounds = cursorAnchorInfo .getCharacterBounds(positionInfoStartIndex + i); if (bounds != null) { - mapRectFromViewToScreenCoords(bounds, true); + if (isCharacterLocationKey) { + mapRectFromViewToScreenCoords(bounds, true); + } else if (isCharacterLocationInWindowKey) { + mapRectFromViewToWindowCoords(bounds, true); + } boundingRects[i] = bounds; } } diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 14505f527195..0f2dd10d7f47 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -169,8 +169,11 @@ public final class TransitionInfo implements Parcelable { /** This change represents its start configuration for the duration of the animation. */ public static final int FLAG_CONFIG_AT_END = 1 << 22; + /** This change represents one of a Task Display Area. */ + public static final int FLAG_IS_TASK_DISPLAY_AREA = 1 << 23; + /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ - public static final int FLAG_FIRST_CUSTOM = 1 << 23; + public static final int FLAG_FIRST_CUSTOM = 1 << 24; /** The change belongs to a window that won't contain activities. */ public static final int FLAGS_IS_NON_APP_WINDOW = @@ -205,6 +208,7 @@ public final class TransitionInfo implements Parcelable { FLAG_MOVED_TO_TOP, FLAG_SYNC, FLAG_CONFIG_AT_END, + FLAG_IS_TASK_DISPLAY_AREA, FLAG_FIRST_CUSTOM }, flag = true) public @interface ChangeFlags {} @@ -553,6 +557,9 @@ public final class TransitionInfo implements Parcelable { if ((flags & FLAG_MOVED_TO_TOP) != 0) { sb.append(sb.length() == 0 ? "" : "|").append("MOVE_TO_TOP"); } + if ((flags & FLAG_IS_TASK_DISPLAY_AREA) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("FLAG_IS_TASK_DISPLAY_AREA"); + } return sb.toString(); } diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java index b6383d9f0754..38685b652c50 100644 --- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java @@ -530,8 +530,26 @@ public final class LocalFloatingToolbarPopup implements FloatingToolbarPopup { int rootViewTopOnWindow = mTmpCoords[1]; int windowLeftOnScreen = rootViewLeftOnScreen - rootViewLeftOnWindow; int windowTopOnScreen = rootViewTopOnScreen - rootViewTopOnWindow; - mCoordsOnWindow.set( - Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen)); + // In some cases, app can have specific Window for Android UI components such as EditText. + // In this case, Window bounds != App bounds. Hence, instead of ensuring non-negative + // PopupWindow coords, app bounds should be used to limit the coords. For instance, + // ____ <- | + // | | |W1 & App bounds + // |___| | + // |W2 | | W2 has smaller bounds and contain EditText where PopupWindow will be opened. + // ---- <-| + // Here, we'll open PopupWindow upwards, but as PopupWindow is anchored based on W2, it + // will have negative Y coords. This negative Y is safe to use because it's still within app + // bounds. However, if it gets out of app bounds, we should clamp it to 0. + Rect appBounds = mContext + .getResources().getConfiguration().windowConfiguration.getAppBounds(); + mCoordsOnWindow.set(x - windowLeftOnScreen, y - windowTopOnScreen); + if (rootViewLeftOnScreen + mCoordsOnWindow.x < appBounds.left) { + mCoordsOnWindow.x = 0; + } + if (rootViewTopOnScreen + mCoordsOnWindow.y < appBounds.top) { + mCoordsOnWindow.y = 0; + } } /** diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index df87a69f02ce..56292c3d0fb2 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -2403,6 +2403,11 @@ SurfaceComposerClient::Transaction* android_view_SurfaceTransaction_getNativeSur } } +static void nativeEnableDebugLogCallPoints(JNIEnv* env, jclass clazz, jlong transactionObj) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + transaction->enableDebugLogCallPoints(); +} + static const JNINativeMethod sSurfaceControlMethods[] = { // clang-format off {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J", @@ -2649,6 +2654,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { {"nativeNotifyShutdown", "()V", (void*)nativeNotifyShutdown }, {"nativeSetLuts", "(JJ[F[I[I[I[I)V", (void*)nativeSetLuts }, + {"nativeEnableDebugLogCallPoints", "(J)V", (void*)nativeEnableDebugLogCallPoints }, // clang-format on }; diff --git a/core/res/Android.bp b/core/res/Android.bp index f6ca8218926c..66c2e12f7cdf 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -171,6 +171,7 @@ android_app { "android.security.flags-aconfig", "com.android.hardware.input.input-aconfig", "aconfig_trade_in_mode_flags", + "ranging_aconfig_flags", ], } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0479318ad9ed..5913992004b8 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2412,6 +2412,16 @@ android:label="@string/permlab_nearby_wifi_devices" android:protectionLevel="dangerous" /> + <!-- Required to be able to range to devices using generic ranging module. + @FlaggedApi("android.permission.flags.ranging_permission_enabled") + <p>Protection level: dangerous --> + <permission android:name="android.permission.RANGING" + android:permissionGroup="android.permission-group.UNDEFINED" + android:description="@string/permdesc_ranging" + android:label="@string/permlab_ranging" + android:protectionLevel="dangerous" + android:featureFlag="android.permission.flags.ranging_permission_enabled"/> + <!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the user from using them until they are unsuspended. @hide diff --git a/core/res/res/layout/input_method_switch_item_new.xml b/core/res/res/layout/input_method_switch_item_new.xml index f8710cc45358..7b241aff3fb1 100644 --- a/core/res/res/layout/input_method_switch_item_new.xml +++ b/core/res/res/layout/input_method_switch_item_new.xml @@ -18,18 +18,20 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/list_item" android:layout_width="match_parent" - android:layout_height="72dp" + android:layout_height="wrap_content" + android:minHeight="72dp" android:background="@drawable/input_method_switch_item_background" android:gravity="center_vertical" android:orientation="horizontal" android:layout_marginHorizontal="16dp" android:layout_marginBottom="8dp" android:paddingStart="20dp" - android:paddingEnd="24dp"> + android:paddingEnd="24dp" + android:paddingVertical="8dp"> <LinearLayout android:layout_width="0dp" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:layout_weight="1" android:gravity="start|center_vertical" android:orientation="vertical"> @@ -39,11 +41,26 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="marquee" + android:marqueeRepeatLimit="1" android:singleLine="true" android:fontFamily="google-sans-text" android:textColor="@color/input_method_switch_on_item" android:textAppearance="?attr/textAppearanceListItem"/> + <TextView + android:id="@+id/text2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:ellipsize="marquee" + android:marqueeRepeatLimit="1" + android:singleLine="true" + android:fontFamily="google-sans-text" + android:textColor="?attr/materialColorOnSurfaceVariant" + android:textAppearance="?attr/textAppearanceListItemSecondary" + android:textAllCaps="true" + android:visibility="gone"/> + </LinearLayout> <ImageView diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 092d2a72580a..e6dedce8feaf 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4418,6 +4418,10 @@ <declare-styleable name="InputMethod_Subtype"> <!-- The name of the subtype. --> <attr name="label" /> + <!-- The layout label of the subtype. + {@link android.view.inputmethod.InputMethodSubtype#getLayoutDisplayName} returns the + value specified in this attribute. --> + <attr name="layoutLabel" format="reference" /> <!-- The icon of the subtype. --> <attr name="icon" /> <!-- The locale of the subtype. This string should be a locale (for example en_US and fr_FR) diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 0c28ea406aa2..70cc5f14391d 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -125,6 +125,8 @@ <public name="supplementalDescription"/> <!-- @FlaggedApi("android.security.enable_intent_matching_flags") --> <public name="intentMatchingFlags"/> + <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) --> + <public name="layoutLabel"/> </staging-public-group> <staging-public-group type="id" first-id="0x01b60000"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e23e665e7335..c13fdb17dfe3 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1766,6 +1766,11 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=140]--> <string name="permdesc_nearby_wifi_devices">Allows the app to advertise, connect, and determine the relative position of nearby Wi\u2011Fi devices</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50]--> + <string name="permlab_ranging">determine relative position between nearby devices</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=120]--> + <string name="permdesc_ranging">Allow the app to determine relative position between nearby devices</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_preferredPaymentInfo">Preferred NFC Payment Service Information</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 24f6ceaf786c..8d045f87063b 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -26,6 +26,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -59,6 +60,7 @@ import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.StopActivityItem; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -67,7 +69,11 @@ import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.os.Bundle; import android.os.IBinder; +import android.os.Looper; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import android.util.DisplayMetrics; import android.util.Log; @@ -129,6 +135,9 @@ public class ActivityThreadTest { @Rule(order = 1) public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private ActivityWindowInfoListener mActivityWindowInfoListener; private WindowTokenClientController mOriginalWindowTokenClientController; private Configuration mOriginalAppConfig; @@ -912,6 +921,32 @@ public class ActivityThreadTest { } /** + * Verifies that {@link ActivityThread#handleApplicationInfoChanged} does send updates to the + * system context, when given the system application info. + */ + @RequiresFlagsEnabled(android.content.res.Flags.FLAG_SYSTEM_CONTEXT_HANDLE_APP_INFO_CHANGED) + @Test + public void testHandleApplicationInfoChanged_systemContext() { + Looper.prepare(); + final var systemThread = ActivityThread.createSystemActivityThreadForTesting(); + + final Context systemContext = systemThread.getSystemContext(); + final var appInfo = systemContext.getApplicationInfo(); + // sourceDir must not be null, and contain at least a '/', for handleApplicationInfoChanged. + appInfo.sourceDir = "/"; + + // Create a copy of the application info. + final var newAppInfo = new ApplicationInfo(appInfo); + newAppInfo.sourceDir = "/"; + assertWithMessage("New application info is a separate instance") + .that(systemContext.getApplicationInfo()).isNotSameInstanceAs(newAppInfo); + + systemThread.handleApplicationInfoChanged(newAppInfo); + assertWithMessage("Application info was updated successfully") + .that(systemContext.getApplicationInfo()).isSameInstanceAs(newAppInfo); + } + + /** * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord, * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the * activity for the given sequence number. diff --git a/core/tests/coretests/src/android/view/RoundScrollbarRendererTest.java b/core/tests/coretests/src/android/view/RoundScrollbarRendererTest.java index 262bd5cd6c01..0f17f9cdddc8 100644 --- a/core/tests/coretests/src/android/view/RoundScrollbarRendererTest.java +++ b/core/tests/coretests/src/android/view/RoundScrollbarRendererTest.java @@ -16,7 +16,11 @@ package android.view; +import static android.view.RoundScrollbarRenderer.BLUECHIP_ENABLED_SYSPROP; + import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; @@ -30,11 +34,8 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import android.os.SystemProperties; import android.platform.test.annotations.Presubmit; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.view.flags.Flags; import androidx.test.core.app.ApplicationProvider; @@ -42,7 +43,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -66,9 +66,6 @@ public class RoundScrollbarRendererTest { private static final float DEFAULT_ALPHA = 0.5f; private static final Rect BOUNDS = new Rect(0, 0, 200, 200); - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - @Mock private Canvas mCanvas; @Captor private ArgumentCaptor<Paint> mPaintCaptor; private RoundScrollbarRenderer mScrollbar; @@ -88,8 +85,8 @@ public class RoundScrollbarRendererTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_USE_REFACTORED_ROUND_SCROLLBAR) public void testScrollbarDrawn_legacy() { + assumeFalse(usingRefactoredScrollbar()); mScrollbar.drawRoundScrollbars(mCanvas, DEFAULT_ALPHA, BOUNDS, /* drawToLeft= */ false); // The arc will be drawn twice, i.e. once for track and once for thumb @@ -105,8 +102,8 @@ public class RoundScrollbarRendererTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_USE_REFACTORED_ROUND_SCROLLBAR) public void testScrollbarDrawn() { + assumeTrue(usingRefactoredScrollbar()); mScrollbar.drawRoundScrollbars(mCanvas, DEFAULT_ALPHA, BOUNDS, /* drawToLeft= */ false); // The arc will be drawn thrice, i.e. twice for track and once for thumb @@ -143,4 +140,9 @@ public class RoundScrollbarRendererTest { return super.computeVerticalScrollExtent(); } } + + private static boolean usingRefactoredScrollbar() { + return Flags.useRefactoredRoundScrollbar() + && SystemProperties.getBoolean(BLUECHIP_ENABLED_SYSPROP, false); + } } diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index 93d94c9cd7eb..b4899f975f43 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -19,6 +19,8 @@ package android.graphics; import android.annotation.FlaggedApi; import android.annotation.IntDef; +import com.android.internal.camera.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -63,6 +65,7 @@ public class ImageFormat { RAW_DEPTH10, PRIVATE, HEIC, + HEIC_ULTRAHDR, JPEG_R }) public @interface Format { @@ -832,6 +835,16 @@ public class ImageFormat { public static final int HEIC = 0x48454946; /** + * High Efficiency Image File Format (HEIF) with embedded HDR gain map + * + * <p>This format defines the HEIC brand of High Efficiency Image File + * Format as described in ISO/IEC 23008-12:2024 with HDR gain map according + * to ISO/CD 21496‐1.</p> + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final int HEIC_ULTRAHDR = 0x1006; + + /** * Use this function to retrieve the number of bits per pixel of an * ImageFormat. * @@ -926,6 +939,11 @@ public class ImageFormat { if (android.media.codec.Flags.p210FormatSupport() && format == YCBCR_P210) { return true; } + if (Flags.cameraHeifGainmap()){ + if (format == HEIC_ULTRAHDR) { + return true; + } + } return false; } } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index b7a1c13c75c7..8bb32568ec5a 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -19,7 +19,7 @@ package android.graphics; import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION; import static com.android.text.flags.Flags.FLAG_DEPRECATE_ELEGANT_TEXT_HEIGHT_API; - +import static com.android.text.flags.Flags.FLAG_VERTICAL_TEXT_LAYOUT; import android.annotation.ColorInt; import android.annotation.ColorLong; @@ -35,6 +35,7 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.fonts.FontVariationAxis; +import android.graphics.text.TextRunShaper; import android.os.Build; import android.os.LocaleList; import android.text.GraphicsOperations; @@ -269,7 +270,24 @@ public class Paint { public static final int EMBEDDED_BITMAP_TEXT_FLAG = 0x400; /** @hide bit mask for the flag forcing freetype's autohinter on for text */ public static final int AUTO_HINTING_TEXT_FLAG = 0x800; - /** @hide bit mask for the flag enabling vertical rendering for text */ + + /** + * A flat that controls text to be written in vertical orientation + * + * <p> + * This flag is used for telling the underlying text layout engine that the text is for vertical + * direction. By enabling this flag, text measurement, drawing and shaping APIs works for + * vertical text layout. For example, {@link Canvas#drawText(String, float, float, Paint)} draws + * text from top to bottom. {@link Paint#measureText(String)} returns vertical advances instead + * of horizontal advances. {@link TextRunShaper} shapes text vertically and report glyph IDs for + * vertical layout. + * + * <p> + * Do not set this flag for making {@link android.text.Layout}. The {@link android.text.Layout} + * class and its subclasses are designed for horizontal text only and does not work for vertical + * text. + */ + @FlaggedApi(FLAG_VERTICAL_TEXT_LAYOUT) public static final int VERTICAL_TEXT_FLAG = 0x1000; /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java index 438532725686..4d7be39ca5a4 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java @@ -16,6 +16,7 @@ package androidx.window.extensions.area; +import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY; import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT; import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY; import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY; @@ -569,7 +570,8 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, private boolean isDeviceFolded() { if (Flags.deviceStatePropertyApi()) { return mCurrentDeviceState.hasProperty( - PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY); + PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY) + && !mCurrentDeviceState.hasProperty(PROPERTY_EMULATED_ONLY); } else { return ArrayUtils.contains(mFoldedDeviceStates, mCurrentDeviceState.getIdentifier()); } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt index 2fbf089d99d6..00d9a931cebe 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt @@ -19,12 +19,15 @@ package com.android.wm.shell.bubbles.bar import android.app.ActivityManager import android.content.Context import android.content.pm.LauncherApps +import android.graphics.PointF import android.os.Handler import android.os.UserManager import android.view.IWindowManager import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.WindowManager +import androidx.core.animation.AnimatorTestRule import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -48,6 +51,7 @@ import com.android.wm.shell.bubbles.BubbleTaskViewFactory import com.android.wm.shell.bubbles.Bubbles.SysuiProxy import com.android.wm.shell.bubbles.FakeBubbleFactory import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat +import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix import com.android.wm.shell.bubbles.properties.BubbleProperties import com.android.wm.shell.bubbles.storage.BubblePersistentRepository import com.android.wm.shell.common.DisplayController @@ -57,6 +61,7 @@ import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.TaskStackListenerImpl import com.android.wm.shell.shared.TransactionPool +import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils import com.android.wm.shell.shared.bubbles.BubbleBarLocation import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController @@ -66,8 +71,10 @@ import com.android.wm.shell.taskview.TaskViewTaskController import com.android.wm.shell.taskview.TaskViewTransitions import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat +import org.junit.After import java.util.Collections import org.junit.Before +import org.junit.ClassRule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock @@ -79,18 +86,28 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class BubbleBarLayerViewTest { + companion object { + @JvmField @ClassRule + val animatorTestRule: AnimatorTestRule = AnimatorTestRule() + } + private val context = ApplicationProvider.getApplicationContext<Context>() private lateinit var bubbleBarLayerView: BubbleBarLayerView private lateinit var uiEventLoggerFake: UiEventLoggerFake + private lateinit var bubbleController: BubbleController + + private lateinit var bubblePositioner: BubblePositioner + private lateinit var bubble: Bubble @Before fun setUp() { ProtoLog.REQUIRE_PROTOLOGTOOL = false ProtoLog.init() + PhysicsAnimatorTestUtils.prepareForTest() uiEventLoggerFake = UiEventLoggerFake() val bubbleLogger = BubbleLogger(uiEventLoggerFake) @@ -100,7 +117,7 @@ class BubbleBarLayerViewTest { val windowManager = context.getSystemService(WindowManager::class.java) - val bubblePositioner = BubblePositioner(context, windowManager) + bubblePositioner = BubblePositioner(context, windowManager) bubblePositioner.setShowingInBubbleBar(true) val bubbleData = @@ -113,7 +130,7 @@ class BubbleBarLayerViewTest { bgExecutor, ) - val bubbleController = + bubbleController = createBubbleController( bubbleData, windowManager, @@ -151,6 +168,12 @@ class BubbleBarLayerViewTest { bubble = FakeBubbleFactory.createChatBubbleWithViewInfo(context, viewInfo = viewInfo) } + @After + fun tearDown() { + PhysicsAnimatorTestUtils.tearDown() + getInstrumentation().waitForIdleSync() + } + private fun createBubbleController( bubbleData: BubbleData, windowManager: WindowManager?, @@ -224,6 +247,70 @@ class BubbleBarLayerViewTest { assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) } + @Test + fun testEventLogging_dragExpandedViewLeft() { + bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT + + getInstrumentation().runOnMainSync { + bubbleBarLayerView.showExpandedView(bubble) + } + waitForExpandedViewAnimation() + + val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view) + assertThat(handleView).isNotNull() + + // Drag from right to left + handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, rightEdge()) + handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, leftEdge()) + handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, leftEdge()) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.logs[0].eventId) + .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW.id) + assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) + } + + @Test + fun testEventLogging_dragExpandedViewRight() { + bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT + + getInstrumentation().runOnMainSync { + bubbleBarLayerView.showExpandedView(bubble) + } + waitForExpandedViewAnimation() + + val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view) + assertThat(handleView).isNotNull() + + // Drag from left to right + handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, leftEdge()) + handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, rightEdge()) + handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, rightEdge()) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.logs[0].eventId) + .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW.id) + assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) + } + + private fun leftEdge(): PointF { + val screenSize = bubblePositioner.availableRect + return PointF(screenSize.left.toFloat(), screenSize.height() / 2f) + } + + private fun rightEdge(): PointF { + val screenSize = bubblePositioner.availableRect + return PointF(screenSize.right.toFloat(), screenSize.height() / 2f) + } + + private fun waitForExpandedViewAnimation() { + // wait for idle to allow the animation to start + getInstrumentation().waitForIdleSync() + getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(200) } + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd( + AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y) + } + private inner class FakeBubbleTaskViewFactory(private val mainExecutor: ShellExecutor) : BubbleTaskViewFactory { override fun create(): BubbleTaskView { @@ -290,4 +377,9 @@ class BubbleBarLayerViewTest { } } } + + private fun View.dispatchTouchEvent(eventTime: Long, action: Int, point: PointF) { + val event = MotionEvent.obtain(0L, eventTime, action, point.x, point.y, 0) + getInstrumentation().runOnMainSync { dispatchTouchEvent(event) } + } } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt index ecb2b25a02f1..d4cbe6e10971 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt @@ -74,6 +74,7 @@ class BubbleExpandedViewPinControllerTest { @Before fun setUp() { ProtoLog.REQUIRE_PROTOLOGTOOL = false + ProtoLog.init() container = FrameLayout(context) val windowManager = context.getSystemService(WindowManager::class.java) positioner = BubblePositioner(context, windowManager) @@ -85,7 +86,7 @@ class BubbleExpandedViewPinControllerTest { isSmallTablet = false, isLandscape = true, isRtl = false, - insets = Insets.of(10, 20, 30, 40) + insets = Insets.of(10, 20, 30, 40), ) positioner.update(deviceConfig) positioner.bubbleBarTopOnScreen = @@ -407,12 +408,26 @@ class BubbleExpandedViewPinControllerTest { assertThat(testListener.locationReleases).containsExactly(RIGHT) } + /** Send drag start event when on left */ + @Test + fun start_onLeft_sendStartEventOnLeft() { + getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = true) } + assertThat(testListener.locationStart).containsExactly(LEFT) + } + + /** Send drag start event when on right */ + @Test + fun start_onRight_sendStartEventOnRight() { + getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) } + assertThat(testListener.locationStart).containsExactly(RIGHT) + } + private fun getExpectedDropTargetBoundsOnLeft(): Rect = Rect().also { positioner.getBubbleBarExpandedViewBounds( true /* onLeft */, false /* isOverflowExpanded */, - it + it, ) } @@ -421,7 +436,7 @@ class BubbleExpandedViewPinControllerTest { positioner.getBubbleBarExpandedViewBounds( false /* onLeft */, false /* isOverflowExpanded */, - it + it, ) } @@ -446,8 +461,14 @@ class BubbleExpandedViewPinControllerTest { } internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener { + val locationStart = mutableListOf<BubbleBarLocation>() val locationChanges = mutableListOf<BubbleBarLocation>() val locationReleases = mutableListOf<BubbleBarLocation>() + + override fun onStart(location: BubbleBarLocation) { + locationStart.add(location) + } + override fun onChange(location: BubbleBarLocation) { locationChanges.add(location) } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl index e21bf8fb723c..93e635dd937c 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl @@ -16,4 +16,4 @@ package com.android.wm.shell.shared; -parcelable GroupedRecentTaskInfo;
\ No newline at end of file +parcelable GroupedTaskInfo;
\ No newline at end of file diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java index 65e079ef4f72..03e0ab0591a1 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java @@ -17,7 +17,8 @@ package com.android.wm.shell.shared; import android.annotation.IntDef; -import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; +import android.app.TaskInfo; import android.app.WindowConfiguration; import android.os.Parcel; import android.os.Parcelable; @@ -27,69 +28,91 @@ import androidx.annotation.Nullable; import com.android.wm.shell.shared.split.SplitBounds; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Set; /** - * Simple container for recent tasks. May contain either a single or pair of tasks. + * Simple container for recent tasks which should be presented as a single task within the + * Overview UI. */ -public class GroupedRecentTaskInfo implements Parcelable { +public class GroupedTaskInfo implements Parcelable { - public static final int TYPE_SINGLE = 1; + public static final int TYPE_FULLSCREEN = 1; public static final int TYPE_SPLIT = 2; public static final int TYPE_FREEFORM = 3; @IntDef(prefix = {"TYPE_"}, value = { - TYPE_SINGLE, + TYPE_FULLSCREEN, TYPE_SPLIT, TYPE_FREEFORM }) public @interface GroupType {} + /** + * The type of this particular task info, can be one of TYPE_FULLSCREEN, TYPE_SPLIT or + * TYPE_FREEFORM. + */ + @GroupType + protected final int mType; + + /** + * The list of tasks associated with this single recent task info. + * TYPE_FULLSCREEN: Contains the stack of tasks associated with a single "task" in overview + * TYPE_SPLIT: Contains the two split roots of each side + * TYPE_FREEFORM: Contains the set of tasks currently in freeform mode + */ @NonNull - private final ActivityManager.RecentTaskInfo[] mTasks; + protected final List<TaskInfo> mTasks; + + /** + * Only set for TYPE_SPLIT. + * + * Information about the split bounds. + */ @Nullable - private final SplitBounds mSplitBounds; - @GroupType - private final int mType; - // TODO(b/348332802): move isMinimized inside each Task object instead once we have a - // replacement for RecentTaskInfo - private final int[] mMinimizedTaskIds; + protected final SplitBounds mSplitBounds; /** - * Create new for a single task + * Only set for TYPE_FREEFORM. + * + * TODO(b/348332802): move isMinimized inside each Task object instead once we have a + * replacement for RecentTaskInfo */ - public static GroupedRecentTaskInfo forSingleTask( - @NonNull ActivityManager.RecentTaskInfo task) { - return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task}, null, - TYPE_SINGLE, null /* minimizedFreeformTasks */); + @Nullable + protected final int[] mMinimizedTaskIds; + + /** + * Create new for a stack of fullscreen tasks + */ + public static GroupedTaskInfo forFullscreenTasks(@NonNull TaskInfo task) { + return new GroupedTaskInfo(List.of(task), null, TYPE_FULLSCREEN, + null /* minimizedFreeformTasks */); } /** * Create new for a pair of tasks in split screen */ - public static GroupedRecentTaskInfo forSplitTasks(@NonNull ActivityManager.RecentTaskInfo task1, - @NonNull ActivityManager.RecentTaskInfo task2, @Nullable SplitBounds splitBounds) { - return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task1, task2}, - splitBounds, TYPE_SPLIT, null /* minimizedFreeformTasks */); + public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1, + @NonNull TaskInfo task2, @Nullable SplitBounds splitBounds) { + return new GroupedTaskInfo(List.of(task1, task2), splitBounds, TYPE_SPLIT, + null /* minimizedFreeformTasks */); } /** * Create new for a group of freeform tasks */ - public static GroupedRecentTaskInfo forFreeformTasks( - @NonNull ActivityManager.RecentTaskInfo[] tasks, - @NonNull Set<Integer> minimizedFreeformTasks) { - return new GroupedRecentTaskInfo( - tasks, - null /* splitBounds */, - TYPE_FREEFORM, + public static GroupedTaskInfo forFreeformTasks( + @NonNull List<TaskInfo> tasks, + @NonNull Set<Integer> minimizedFreeformTasks) { + return new GroupedTaskInfo(tasks, null /* splitBounds */, TYPE_FREEFORM, minimizedFreeformTasks.stream().mapToInt(i -> i).toArray()); } - private GroupedRecentTaskInfo( - @NonNull ActivityManager.RecentTaskInfo[] tasks, + private GroupedTaskInfo( + @NonNull List<TaskInfo> tasks, @Nullable SplitBounds splitBounds, @GroupType int type, @Nullable int[] minimizedFreeformTaskIds) { @@ -100,52 +123,56 @@ public class GroupedRecentTaskInfo implements Parcelable { ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds); } - private static void ensureAllMinimizedIdsPresent( - @NonNull ActivityManager.RecentTaskInfo[] tasks, + private void ensureAllMinimizedIdsPresent( + @NonNull List<TaskInfo> tasks, @Nullable int[] minimizedFreeformTaskIds) { if (minimizedFreeformTaskIds == null) { return; } if (!Arrays.stream(minimizedFreeformTaskIds).allMatch( - taskId -> Arrays.stream(tasks).anyMatch(task -> task.taskId == taskId))) { + taskId -> tasks.stream().anyMatch(task -> task.taskId == taskId))) { throw new IllegalArgumentException("Minimized task IDs contain non-existent Task ID."); } } - GroupedRecentTaskInfo(Parcel parcel) { - mTasks = parcel.createTypedArray(ActivityManager.RecentTaskInfo.CREATOR); + protected GroupedTaskInfo(@NonNull Parcel parcel) { + mTasks = new ArrayList(); + final int numTasks = parcel.readInt(); + for (int i = 0; i < numTasks; i++) { + mTasks.add(new TaskInfo(parcel)); + } mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR); mType = parcel.readInt(); mMinimizedTaskIds = parcel.createIntArray(); } /** - * Get primary {@link ActivityManager.RecentTaskInfo} + * Get primary {@link RecentTaskInfo} */ @NonNull - public ActivityManager.RecentTaskInfo getTaskInfo1() { - return mTasks[0]; + public TaskInfo getTaskInfo1() { + return mTasks.getFirst(); } /** - * Get secondary {@link ActivityManager.RecentTaskInfo}. + * Get secondary {@link RecentTaskInfo}. * * Used in split screen. */ @Nullable - public ActivityManager.RecentTaskInfo getTaskInfo2() { - if (mTasks.length > 1) { - return mTasks[1]; + public TaskInfo getTaskInfo2() { + if (mTasks.size() > 1) { + return mTasks.get(1); } return null; } /** - * Get all {@link ActivityManager.RecentTaskInfo}s grouped together. + * Get all {@link RecentTaskInfo}s grouped together. */ @NonNull - public List<ActivityManager.RecentTaskInfo> getTaskInfoList() { - return Arrays.asList(mTasks); + public List<TaskInfo> getTaskInfoList() { + return mTasks; } /** @@ -164,28 +191,46 @@ public class GroupedRecentTaskInfo implements Parcelable { return mType; } + @Nullable public int[] getMinimizedTaskIds() { return mMinimizedTaskIds; } @Override + public boolean equals(Object obj) { + if (!(obj instanceof GroupedTaskInfo)) { + return false; + } + GroupedTaskInfo other = (GroupedTaskInfo) obj; + return mType == other.mType + && Objects.equals(mTasks, other.mTasks) + && Objects.equals(mSplitBounds, other.mSplitBounds) + && Arrays.equals(mMinimizedTaskIds, other.mMinimizedTaskIds); + } + + @Override + public int hashCode() { + return Objects.hash(mType, mTasks, mSplitBounds, Arrays.hashCode(mMinimizedTaskIds)); + } + + @Override public String toString() { StringBuilder taskString = new StringBuilder(); - for (int i = 0; i < mTasks.length; i++) { + for (int i = 0; i < mTasks.size(); i++) { if (i == 0) { taskString.append("Task"); } else { taskString.append(", Task"); } - taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks[i])); + taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks.get(i))); } if (mSplitBounds != null) { taskString.append(", SplitBounds: ").append(mSplitBounds); } taskString.append(", Type="); switch (mType) { - case TYPE_SINGLE: - taskString.append("TYPE_SINGLE"); + case TYPE_FULLSCREEN: + taskString.append("TYPE_FULLSCREEN"); break; case TYPE_SPLIT: taskString.append("TYPE_SPLIT"); @@ -199,7 +244,7 @@ public class GroupedRecentTaskInfo implements Parcelable { return taskString.toString(); } - private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) { + private String getTaskInfo(TaskInfo taskInfo) { if (taskInfo == null) { return null; } @@ -213,7 +258,12 @@ public class GroupedRecentTaskInfo implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeTypedArray(mTasks, flags); + // We don't use the parcel list methods because we want to only write the TaskInfo state + // and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated + parcel.writeInt(mTasks.size()); + for (int i = 0; i < mTasks.size(); i++) { + mTasks.get(i).writeTaskToParcel(parcel, flags); + } parcel.writeTypedObject(mSplitBounds, flags); parcel.writeInt(mType); parcel.writeIntArray(mMinimizedTaskIds); @@ -224,13 +274,15 @@ public class GroupedRecentTaskInfo implements Parcelable { return 0; } - public static final @android.annotation.NonNull Creator<GroupedRecentTaskInfo> CREATOR = - new Creator<GroupedRecentTaskInfo>() { - public GroupedRecentTaskInfo createFromParcel(Parcel source) { - return new GroupedRecentTaskInfo(source); + public static final Creator<GroupedTaskInfo> CREATOR = new Creator() { + @Override + public GroupedTaskInfo createFromParcel(Parcel in) { + return new GroupedTaskInfo(in); } - public GroupedRecentTaskInfo[] newArray(int size) { - return new GroupedRecentTaskInfo[size]; + + @Override + public GroupedTaskInfo[] newArray(int size) { + return new GroupedTaskInfo[size]; } }; -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt index fc3dc1465dff..f93b35e868f6 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt @@ -20,7 +20,7 @@ import android.os.Looper import android.util.ArrayMap import androidx.dynamicanimation.animation.FloatPropertyCompat import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils.prepareForTest -import java.util.* +import java.util.ArrayDeque import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.collections.ArrayList @@ -74,14 +74,17 @@ object PhysicsAnimatorTestUtils { @JvmStatic fun tearDown() { - val latch = CountDownLatch(1) - animationThreadHandler.post { + if (Looper.myLooper() == animationThreadHandler.looper) { animatorTestHelpers.keys.forEach { it.cancel() } - latch.countDown() + } else { + val latch = CountDownLatch(1) + animationThreadHandler.post { + animatorTestHelpers.keys.forEach { it.cancel() } + latch.countDown() + } + latch.await(5, TimeUnit.SECONDS) } - latch.await() - animatorTestHelpers.clear() animators.clear() allAnimatedObjects.clear() @@ -348,8 +351,9 @@ object PhysicsAnimatorTestUtils { * Returns all of the values that have ever been reported to update listeners, per property. */ @Suppress("UNCHECKED_CAST") - fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>): - UpdateFramesPerProperty<T> { + fun <T : Any> getAnimationUpdateFrames( + animator: PhysicsAnimator<T> + ): UpdateFramesPerProperty<T> { return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T> } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt index 7086691e7431..bd129a28f049 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt @@ -56,6 +56,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi onLeft = initialLocationOnLeft screenCenterX = screenSizeProvider.invoke().x / 2 dismissZone = getExclusionRect() + listener?.onStart(if (initialLocationOnLeft) LEFT else RIGHT) } /** View has moved to [x] and [y] screen coordinates */ @@ -109,6 +110,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi /** Get width for exclusion rect where dismiss takes over drag */ protected abstract fun getExclusionRectWidth(): Float + /** Get height for exclusion rect where dismiss takes over drag */ protected abstract fun getExclusionRectHeight(): Float @@ -184,6 +186,9 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi /** Receive updates on location changes */ interface LocationChangeListener { + /** Bubble bar dragging has started. Includes the initial location of the bar */ + fun onStart(location: BubbleBarLocation) {} + /** * Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in * progress. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 402818c80b01..999ce17905ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -124,18 +124,7 @@ public class BubbleBarLayerView extends FrameLayout mBubbleExpandedViewPinController = new BubbleExpandedViewPinController( context, this, mPositioner); - mBubbleExpandedViewPinController.setListener( - new BaseBubblePinController.LocationChangeListener() { - @Override - public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) { - mBubbleController.animateBubbleBarLocation(bubbleBarLocation); - } - - @Override - public void onRelease(@NonNull BubbleBarLocation location) { - mBubbleController.setBubbleBarLocation(location); - } - }); + mBubbleExpandedViewPinController.setListener(new LocationChangeListener()); setOnClickListener(view -> hideModalOrCollapse()); } @@ -238,11 +227,7 @@ public class BubbleBarLayerView extends FrameLayout DragListener dragListener = inDismiss -> { if (inDismiss && mExpandedBubble != null) { mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE); - if (mExpandedBubble instanceof Bubble) { - // Only a bubble can be dragged to dismiss - mBubbleLogger.log((Bubble) mExpandedBubble, - BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW); - } + logBubbleEvent(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW); } }; mDragController = new BubbleBarExpandedViewDragController( @@ -423,10 +408,47 @@ public class BubbleBarLayerView extends FrameLayout } } + /** + * Log the event only if {@link #mExpandedBubble} is a {@link Bubble}. + * <p> + * Skips logging if it is {@link BubbleOverflow}. + */ + private void logBubbleEvent(BubbleLogger.Event event) { + if (mExpandedBubble != null && mExpandedBubble instanceof Bubble bubble) { + mBubbleLogger.log(bubble, event); + } + } + @Nullable @VisibleForTesting public BubbleBarExpandedViewDragController getDragController() { return mDragController; } + private class LocationChangeListener implements + BaseBubblePinController.LocationChangeListener { + + private BubbleBarLocation mInitialLocation; + + @Override + public void onStart(@NonNull BubbleBarLocation location) { + mInitialLocation = location; + } + + @Override + public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) { + mBubbleController.animateBubbleBarLocation(bubbleBarLocation); + } + + @Override + public void onRelease(@NonNull BubbleBarLocation location) { + mBubbleController.setBubbleBarLocation(location); + if (location != mInitialLocation) { + BubbleLogger.Event event = location.isOnLeft(isLayoutRtl()) + ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW + : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW; + logBubbleEvent(event); + } + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt index b83b5f341dda..8ef20d1d6b93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -44,7 +44,8 @@ object PipUtils { private const val TAG = "PipUtils" // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. - private const val EPSILON = 1e-7 + // TODO b/377530560: Restore epsilon once a long term fix is merged for non-config-at-end issue. + private const val EPSILON = 0.05f /** * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 886330f3264a..0200e18b5c50 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -286,7 +286,7 @@ public class CompatUIController implements OnDisplaysChangedListener, // we need to ignore all the incoming TaskInfo until the education // completes. If we come from a double tap we follow the normal flow. final boolean topActivityPillarboxed = - taskInfo.appCompatTaskInfo.isTopActivityPillarboxed(); + taskInfo.appCompatTaskInfo.isTopActivityPillarboxShaped(); final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed && !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo); final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 706a67821a30..a472f79c98e6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -779,7 +779,8 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, ReturnToDragStartAnimator returnToDragStartAnimator, - @DynamicOverride DesktopRepository desktopRepository) { + @DynamicOverride DesktopRepository desktopRepository, + DesktopModeEventLogger desktopModeEventLogger) { return new DesktopTilingDecorViewModel( context, displayController, @@ -789,7 +790,8 @@ public abstract class WMShellModule { shellTaskOrganizer, toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, - desktopRepository + desktopRepository, + desktopModeEventLogger ); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt index 48bb2a8b4a74..cefcb757690f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt @@ -205,6 +205,11 @@ class DesktopMixedTransitionHandler( finishTransaction: SurfaceControl.Transaction, finishCallback: TransitionFinishCallback, ): Boolean { + val launchChange = findDesktopTaskChange(info, pending.launchingTask) + if (launchChange == null) { + logV("No launch Change, returning") + return false + } // Check if there's also an immersive change during this launch. val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask -> findDesktopTaskChange(info, exitingTask) @@ -212,8 +217,6 @@ class DesktopMixedTransitionHandler( val minimizeChange = pending.minimizingTask?.let { minimizingTask -> findDesktopTaskChange(info, minimizingTask) } - val launchChange = findDesktopTaskChange(info, pending.launchingTask) - ?: error("Should have pending launching task change") var subAnimationCount = -1 var combinedWct: WindowContainerTransaction? = null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index edcc877ef58e..c7cf31081c8b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -275,7 +275,7 @@ private fun TaskInfo.hasPortraitTopActivity(): Boolean { } // Then check if the activity is portrait when letterboxed - appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed + appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxShaped // Then check if the activity is portrait appBounds != null -> appBounds.height() > appBounds.width() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index f8d2011d0934..b618bf1215ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -53,6 +53,7 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.protolog.ProtoLog; +import com.android.window.flags.Flags; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; @@ -72,6 +73,9 @@ import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; public class KeyguardTransitionHandler implements Transitions.TransitionHandler, KeyguardChangeListener, TaskStackListenerCallback { + private static final boolean ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS = + Flags.ensureKeyguardDoesTransitionStarting(); + private static final String TAG = "KeyguardTransition"; private final Transitions mTransitions; @@ -194,7 +198,7 @@ public class KeyguardTransitionHandler // Occlude/unocclude animations are only played if the keyguard is locked. if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { - if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) { + if (isKeyguardOccluding(info)) { if (hasOpeningDream(info)) { return startAnimation(mOccludeByDreamTransition, "occlude-by-dream", transition, info, startTransaction, finishTransaction, finishCallback); @@ -202,7 +206,7 @@ public class KeyguardTransitionHandler return startAnimation(mOccludeTransition, "occlude", transition, info, startTransaction, finishTransaction, finishCallback); } - } else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) { + } else if (isKeyguardUnoccluding(info)) { return startAnimation(mUnoccludeTransition, "unocclude", transition, info, startTransaction, finishTransaction, finishCallback); } @@ -325,6 +329,36 @@ public class KeyguardTransitionHandler return false; } + private static boolean isKeyguardOccluding(@NonNull TransitionInfo info) { + if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { + return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0; + } + + for (int i = 0; i < info.getChanges().size(); i++) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA) + && change.getMode() == TRANSIT_TO_FRONT) { + return true; + } + } + return false; + } + + private static boolean isKeyguardUnoccluding(@NonNull TransitionInfo info) { + if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { + return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0; + } + + for (int i = 0; i < info.getChanges().size(); i++) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA) + && change.getMode() == TRANSIT_TO_BACK) { + return true; + } + } + return false; + } + private void finishAnimationImmediately(IBinder transition, StartedTransition playing) { final IBinder fakeTransition = new Binder(); final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java index 24077a35d41c..026482004d51 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java @@ -24,7 +24,6 @@ import android.view.Choreographer; import android.view.SurfaceControl; import com.android.wm.shell.R; -import com.android.wm.shell.transition.Transitions; /** * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition. @@ -180,8 +179,7 @@ public class PipSurfaceTransactionHelper { // destination are different. final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH; final Rect crop = mTmpDestinationRect; - crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH - : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH); + crop.set(0, 0, destW, destH); // Inverse scale for crop to fit in screen coordinates. crop.scale(1 / scale); crop.offset(insets.left, insets.top); @@ -200,8 +198,8 @@ public class PipSurfaceTransactionHelper { } } mTmpTransform.setScale(scale, scale); - mTmpTransform.postRotate(degrees); mTmpTransform.postTranslate(positionX, positionY); + mTmpTransform.postRotate(degrees); tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setCrop(leash, crop); return this; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java index 3f9b0c30e314..fb1aba399585 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java @@ -16,6 +16,8 @@ package com.android.wm.shell.pip2.animation; +import static android.view.Surface.ROTATION_90; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.RectEvaluator; @@ -73,6 +75,7 @@ public class PipExpandAnimator extends ValueAnimator { mAnimationStartCallback.run(); } if (mStartTransaction != null) { + onExpandAnimationUpdate(mStartTransaction, 0f); mStartTransaction.apply(); } } @@ -81,13 +84,7 @@ public class PipExpandAnimator extends ValueAnimator { public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); if (mFinishTransaction != null) { - // finishTransaction might override some state (eg. corner radii) so we want to - // manually set the state to the end of the animation - mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash, - mSourceRectHint, mBaseBounds, mAnimatedRect, getInsets(1f), - false /* isInPipDirection */, 1f) - .round(mFinishTransaction, mLeash, false /* applyCornerRadius */) - .shadow(mFinishTransaction, mLeash, false /* applyCornerRadius */); + onExpandAnimationUpdate(mFinishTransaction, 1f); } if (mAnimationEndCallback != null) { mAnimationEndCallback.run(); @@ -102,14 +99,7 @@ public class PipExpandAnimator extends ValueAnimator { final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); final float fraction = getAnimatedFraction(); - - // TODO (b/350801661): implement fixed rotation - Rect insets = getInsets(fraction); - mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, - mBaseBounds, mAnimatedRect, - insets, false /* isInPipDirection */, fraction) - .round(tx, mLeash, false /* applyCornerRadius */) - .shadow(tx, mLeash, false /* applyCornerRadius */); + onExpandAnimationUpdate(tx, fraction); tx.apply(); } }; @@ -167,6 +157,32 @@ public class PipExpandAnimator extends ValueAnimator { mAnimationEndCallback = runnable; } + private void onExpandAnimationUpdate(SurfaceControl.Transaction tx, float fraction) { + Rect insets = getInsets(fraction); + if (mRotation == Surface.ROTATION_0) { + mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, mBaseBounds, + mAnimatedRect, insets, false /* isInPipDirection */, fraction); + } else { + // Fixed rotation case. + Rect start = mStartBounds; + Rect end = mEndBounds; + float degrees, x, y; + x = fraction * (end.left - start.left) + start.left; + y = fraction * (end.top - start.top) + start.top; + + if (mRotation == ROTATION_90) { + degrees = 90 * fraction; + } else { + degrees = -90 * fraction; + } + mPipSurfaceTransactionHelper.rotateAndScaleWithCrop(tx, mLeash, mBaseBounds, + mAnimatedRect, insets, degrees, x, y, + true /* isExpanding */, mRotation == ROTATION_90); + } + mPipSurfaceTransactionHelper.round(tx, mLeash, false /* applyCornerRadius */) + .shadow(tx, mLeash, false /* applyShadowRadius */); + } + private Rect getInsets(float fraction) { final Rect startInsets = mSourceRectHintInsets; final Rect endInsets = mZeroInsets; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java index 012dabbbb9f8..4558a9f141c8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java @@ -30,6 +30,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; +import com.android.wm.shell.shared.animation.Interpolators; /** * Animator that handles any resize related animation for PIP. @@ -128,6 +129,7 @@ public class PipResizeAnimator extends ValueAnimator { mRectEvaluator = new RectEvaluator(mAnimatedRect); setObjectValues(startBounds, endBounds); + setInterpolator(Interpolators.FAST_OUT_SLOW_IN); addListener(mAnimatorListener); addUpdateListener(mAnimatorUpdateListener); setEvaluator(mRectEvaluator); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java index 58d2a8577d8c..44900ce1db8a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java @@ -573,7 +573,7 @@ public class PhonePipMenuController implements PipMenuController, @PipTransitionState.TransitionState int newState, Bundle extra) { switch (newState) { case PipTransitionState.ENTERED_PIP: - attach(mPipTransitionState.mPinnedTaskLeash); + attach(mPipTransitionState.getPinnedTaskLeash()); break; case PipTransitionState.EXITED_PIP: detach(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index 9a93371621c9..d3f537b8f904 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -376,18 +376,20 @@ public class PipController implements ConfigurationChangeListener, private void setLauncherKeepClearAreaHeight(boolean visible, int height) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height); - if (visible) { - Rect rect = new Rect( - 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height, - mPipDisplayLayoutState.getDisplayBounds().right, - mPipDisplayLayoutState.getDisplayBounds().bottom); - mPipBoundsState.setNamedUnrestrictedKeepClearArea( - PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect); - } else { - mPipBoundsState.setNamedUnrestrictedKeepClearArea( - PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null); - } - mPipTouchHandler.onShelfVisibilityChanged(visible, height); + mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> { + if (visible) { + Rect rect = new Rect( + 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height, + mPipDisplayLayoutState.getDisplayBounds().right, + mPipDisplayLayoutState.getDisplayBounds().bottom); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect); + } else { + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null); + } + mPipTouchHandler.onShelfVisibilityChanged(visible, height); + }); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 17392bc521d8..3738353dd0a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -785,7 +785,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private void handleFlingTransition(SurfaceControl.Transaction startTx, SurfaceControl.Transaction finishTx, Rect destinationBounds) { - startTx.setPosition(mPipTransitionState.mPinnedTaskLeash, + startTx.setPosition(mPipTransitionState.getPinnedTaskLeash(), destinationBounds.left, destinationBounds.top); startTx.apply(); @@ -799,7 +799,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private void startResizeAnimation(SurfaceControl.Transaction startTx, SurfaceControl.Transaction finishTx, Rect destinationBounds, int duration) { - SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); Preconditions.checkState(pipLeash != null, "No leash cached by mPipTransitionState=" + mPipTransitionState); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java index 751175f0f3e9..d98be55f28e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java @@ -531,7 +531,7 @@ public class PipResizeGestureHandler implements // If resize transition was scheduled from this component, handle leash updates. mWaitingForBoundsChangeTransition = false; - SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); Preconditions.checkState(pipLeash != null, "No leash cached by mPipTransitionState=" + mPipTransitionState); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 8b25b11e3a47..607de0eccd77 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -118,7 +118,7 @@ public class PipScheduler { public void removePipAfterAnimation() { SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); PipAlphaAnimator animator = new PipAlphaAnimator(mContext, - mPipTransitionState.mPinnedTaskLeash, tx, PipAlphaAnimator.FADE_OUT); + mPipTransitionState.getPinnedTaskLeash(), tx, PipAlphaAnimator.FADE_OUT); animator.setAnimationEndCallback(this::scheduleRemovePipImmediately); animator.start(); } @@ -203,7 +203,7 @@ public class PipScheduler { "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG); return; } - SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash(); final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); Matrix transformTensor = new Matrix(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java index 2c7584af1f07..2f9371536a16 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java @@ -29,6 +29,8 @@ import android.view.SurfaceControl; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.Preconditions; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ShellExecutor; @@ -36,6 +38,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip2.animation.PipResizeAnimator; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.annotations.ShellMainThread; import java.util.ArrayList; @@ -49,7 +52,8 @@ import java.util.List; public class PipTaskListener implements ShellTaskOrganizer.TaskListener, PipTransitionState.PipTransitionStateChangedListener { private static final int ASPECT_RATIO_CHANGE_DURATION = 250; - private static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change"; + @VisibleForTesting + static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change"; private final Context mContext; private final PipTransitionState mPipTransitionState; @@ -63,6 +67,8 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, private boolean mWaitingForAspectRatioChange = false; private final List<PipParamsChangedCallback> mPipParamsChangedListeners = new ArrayList<>(); + private PipResizeAnimatorSupplier mPipResizeAnimatorSupplier; + public PipTaskListener(Context context, ShellTaskOrganizer shellTaskOrganizer, PipTransitionState pipTransitionState, @@ -84,6 +90,7 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP); }); } + mPipResizeAnimatorSupplier = PipResizeAnimator::new; } void setPictureInPictureParams(@Nullable PictureInPictureParams params) { @@ -121,6 +128,9 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, if (mPictureInPictureParams.equals(params)) { return; } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "onTaskInfoChanged: %s, state=%s oldParams=%s newParams=%s", + taskInfo.topActivity, mPipTransitionState, mPictureInPictureParams, params); setPictureInPictureParams(params); float newAspectRatio = mPictureInPictureParams.getAspectRatioFloat(); if (PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) { @@ -167,18 +177,18 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION, PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION); - Preconditions.checkNotNull(mPipTransitionState.mPinnedTaskLeash, + Preconditions.checkNotNull(mPipTransitionState.getPinnedTaskLeash(), "Leash is null for bounds transition."); if (mWaitingForAspectRatioChange) { - PipResizeAnimator animator = new PipResizeAnimator(mContext, - mPipTransitionState.mPinnedTaskLeash, startTx, finishTx, + mWaitingForAspectRatioChange = false; + PipResizeAnimator animator = mPipResizeAnimatorSupplier.get(mContext, + mPipTransitionState.getPinnedTaskLeash(), startTx, finishTx, destinationBounds, mPipBoundsState.getBounds(), destinationBounds, duration, 0f /* delta */); - animator.setAnimationEndCallback(() -> { - mPipScheduler.scheduleFinishResizePip(destinationBounds); - }); + animator.setAnimationEndCallback( + () -> mPipScheduler.scheduleFinishResizePip(destinationBounds)); animator.start(); } break; @@ -192,4 +202,22 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, default void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) { } } + + @VisibleForTesting + interface PipResizeAnimatorSupplier { + PipResizeAnimator get(@NonNull Context context, + @NonNull SurfaceControl leash, + @Nullable SurfaceControl.Transaction startTx, + @Nullable SurfaceControl.Transaction finishTx, + @NonNull Rect baseBounds, + @NonNull Rect startBounds, + @NonNull Rect endBounds, + int duration, + float delta); + } + + @VisibleForTesting + void setPipResizeAnimatorSupplier(@NonNull PipResizeAnimatorSupplier supplier) { + mPipResizeAnimatorSupplier = supplier; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 19d293e829ad..65972fb7df48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -231,17 +231,15 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha // KCA triggered movement to wait for other transitions (e.g. due to IME changes). return; } - mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> { - boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip() - || mPipBoundsState.hasUserResizedPip()); - int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top - - mPipBoundsState.getBounds().top; + boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip() + || mPipBoundsState.hasUserResizedPip()); + int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top + - mPipBoundsState.getBounds().top; - if (!mIsImeShowing && !hasUserInteracted && delta != 0) { - // If the user hasn't interacted with PiP, we respect the keep clear areas - mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta); - } - }); + if (!mIsImeShowing && !hasUserInteracted && delta != 0) { + // If the user hasn't interacted with PiP, we respect the keep clear areas + mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta); + } }; if (PipUtils.isPip2ExperimentEnabled()) { @@ -877,7 +875,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mPipBoundsState.getMovementBounds().bottom; mMotionHelper.setSpringingToTouch(false); - mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.mPinnedTaskLeash); + mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.getPinnedTaskLeash()); // If the menu is still visible then just poke the menu // so that it will timeout after the user stops touching it diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 6bf92f69cfb6..ea783e9cadb6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -325,9 +325,7 @@ public class PipTransition extends PipTransitionController implements return false; } - SurfaceControl pipLeash = pipChange.getLeash(); - Preconditions.checkNotNull(pipLeash, "Leash is null for swipe-up transition."); - + final SurfaceControl pipLeash = getLeash(pipChange); final Rect destinationBounds = pipChange.getEndAbsBounds(); final SurfaceControl swipePipToHomeOverlay = mPipTransitionState.getSwipePipToHomeOverlay(); if (swipePipToHomeOverlay != null) { @@ -349,7 +347,7 @@ public class PipTransition extends PipTransitionController implements : startRotation - endRotation; if (delta != ROTATION_0) { mPipTransitionState.setInFixedRotation(true); - handleBoundsTypeFixedRotation(pipChange, pipActivityChange, endRotation); + handleBoundsEnterFixedRotation(pipChange, pipActivityChange, endRotation); } prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange, @@ -399,7 +397,7 @@ public class PipTransition extends PipTransitionController implements final Rect adjustedSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio); - final SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + final SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); // For opening type transitions, if there is a change of mode TO_FRONT/OPEN, // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f @@ -414,15 +412,15 @@ public class PipTransition extends PipTransitionController implements } final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); - int startRotation = pipChange.getStartRotation(); - int endRotation = fixedRotationChange != null + final int startRotation = pipChange.getStartRotation(); + final int endRotation = fixedRotationChange != null ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 : startRotation - endRotation; if (delta != ROTATION_0) { mPipTransitionState.setInFixedRotation(true); - handleBoundsTypeFixedRotation(pipChange, pipActivityChange, + handleBoundsEnterFixedRotation(pipChange, pipActivityChange, fixedRotationChange.getEndFixedRotation()); } @@ -459,7 +457,7 @@ public class PipTransition extends PipTransitionController implements animator.start(); } - private void handleBoundsTypeFixedRotation(TransitionInfo.Change pipTaskChange, + private void handleBoundsEnterFixedRotation(TransitionInfo.Change pipTaskChange, TransitionInfo.Change pipActivityChange, int endRotation) { final Rect endBounds = pipTaskChange.getEndAbsBounds(); final Rect endActivityBounds = pipActivityChange.getEndAbsBounds(); @@ -492,6 +490,26 @@ public class PipTransition extends PipTransitionController implements endBounds.top + activityEndOffset.y); } + private void handleExpandFixedRotation(TransitionInfo.Change pipTaskChange, int endRotation) { + final Rect endBounds = pipTaskChange.getEndAbsBounds(); + final int width = endBounds.width(); + final int height = endBounds.height(); + final int left = endBounds.left; + final int top = endBounds.top; + int newTop, newLeft; + + if (endRotation == Surface.ROTATION_90) { + newLeft = top; + newTop = -(left + width); + } else { + newLeft = -(height + top); + newTop = left; + } + // Modify the endBounds, rotating and placing them potentially off-screen, so that + // as we translate and rotate around the origin, we place them right into the target. + endBounds.set(newLeft, newTop, newLeft + height, newTop + width); + } + private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -503,7 +521,7 @@ public class PipTransition extends PipTransitionController implements } Rect destinationBounds = pipChange.getEndAbsBounds(); - SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); Preconditions.checkNotNull(pipLeash, "Leash is null for alpha transition."); // Start transition with 0 alpha at the entry bounds. @@ -544,33 +562,51 @@ public class PipTransition extends PipTransitionController implements } } - // for multi activity, we need to manually set the leash layer - if (pipChange.getTaskInfo() == null) { - TransitionInfo.Change parent = getChangeByToken(info, pipChange.getParent()); - if (parent != null) { - startTransaction.setLayer(parent.getLeash(), Integer.MAX_VALUE - 1); - } + // The parent change if we were in a multi-activity PiP; null if single activity PiP. + final TransitionInfo.Change parentBeforePip = pipChange.getTaskInfo() == null + ? getChangeByToken(info, pipChange.getParent()) : null; + if (parentBeforePip != null) { + // For multi activity, we need to manually set the leash layer + startTransaction.setLayer(parentBeforePip.getLeash(), Integer.MAX_VALUE - 1); } - Rect startBounds = pipChange.getStartAbsBounds(); - Rect endBounds = pipChange.getEndAbsBounds(); - SurfaceControl pipLeash = pipChange.getLeash(); - Preconditions.checkNotNull(pipLeash, "Leash is null for exit transition."); + final Rect startBounds = pipChange.getStartAbsBounds(); + final Rect endBounds = pipChange.getEndAbsBounds(); + final SurfaceControl pipLeash = getLeash(pipChange); - Rect sourceRectHint = null; - if (pipChange.getTaskInfo() != null - && pipChange.getTaskInfo().pictureInPictureParams != null) { + PictureInPictureParams params = null; + if (pipChange.getTaskInfo() != null) { // single activity - sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint(); - } else if (mPipTaskListener.getPictureInPictureParams().hasSourceBoundsHint()) { + params = pipChange.getTaskInfo().pictureInPictureParams; + } else if (parentBeforePip != null && parentBeforePip.getTaskInfo() != null) { // multi activity - sourceRectHint = mPipTaskListener.getPictureInPictureParams().getSourceRectHint(); + params = parentBeforePip.getTaskInfo().pictureInPictureParams; + } + final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds, + startBounds); + + final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); + final int startRotation = pipChange.getStartRotation(); + final int endRotation = fixedRotationChange != null + ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; + final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 + : endRotation - startRotation; + + if (delta != ROTATION_0) { + handleExpandFixedRotation(pipChange, endRotation); } PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash, startTransaction, finishTransaction, endBounds, startBounds, endBounds, - sourceRectHint, Surface.ROTATION_0); - animator.setAnimationEndCallback(this::finishTransition); + sourceRectHint, delta); + animator.setAnimationEndCallback(() -> { + if (parentBeforePip != null) { + // TODO b/377362511: Animate local leash instead to also handle letterbox case. + // For multi-activity, set the crop to be null + finishTransaction.setCrop(pipLeash, null); + } + finishTransition(); + }); animator.start(); return true; } @@ -717,6 +753,13 @@ public class PipTransition extends PipTransitionController implements } } + @NonNull + private SurfaceControl getLeash(TransitionInfo.Change change) { + SurfaceControl leash = change.getLeash(); + Preconditions.checkNotNull(leash, "Leash is null for change=" + change); + return leash; + } + // // Miscellaneous callbacks and listeners // @@ -754,17 +797,17 @@ public class PipTransition extends PipTransitionController implements mPipTransitionState.mPipTaskToken = extra.getParcelable( PIP_TASK_TOKEN, WindowContainerToken.class); - mPipTransitionState.mPinnedTaskLeash = extra.getParcelable( - PIP_TASK_LEASH, SurfaceControl.class); + mPipTransitionState.setPinnedTaskLeash(extra.getParcelable( + PIP_TASK_LEASH, SurfaceControl.class)); boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null - && mPipTransitionState.mPinnedTaskLeash != null; + && mPipTransitionState.getPinnedTaskLeash() != null; Preconditions.checkState(hasValidTokenAndLeash, "Unexpected bundle for " + mPipTransitionState); break; case PipTransitionState.EXITED_PIP: mPipTransitionState.mPipTaskToken = null; - mPipTransitionState.mPinnedTaskLeash = null; + mPipTransitionState.setPinnedTaskLeash(null); break; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java index ccdd66b5d1a8..03e06f906015 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java @@ -142,7 +142,7 @@ public class PipTransitionState { // pinned PiP task's leash @Nullable - SurfaceControl mPinnedTaskLeash; + private SurfaceControl mPinnedTaskLeash; // Overlay leash potentially used during swipe PiP to home transition; // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid. @@ -304,6 +304,14 @@ public class PipTransitionState { mSwipePipToHomeAppBounds.setEmpty(); } + @Nullable SurfaceControl getPinnedTaskLeash() { + return mPinnedTaskLeash; + } + + void setPinnedTaskLeash(@Nullable SurfaceControl leash) { + mPinnedTaskLeash = leash; + } + /** * @return true if either in swipe or button-nav fixed rotation. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl index 799028a5507a..4a301cc0b603 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl @@ -24,7 +24,7 @@ import android.os.Bundle; import com.android.wm.shell.recents.IRecentsAnimationRunner; import com.android.wm.shell.recents.IRecentTasksListener; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; /** * Interface that is exposed to remote callers to fetch recent tasks. @@ -44,7 +44,7 @@ interface IRecentTasks { /** * Gets the set of recent tasks. */ - GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3; + GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3; /** * Gets the set of running tasks. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl index 371bdd5c6469..b58f0681c571 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl @@ -18,6 +18,8 @@ package com.android.wm.shell.recents; import android.app.ActivityManager.RunningTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; + /** * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks. */ @@ -44,8 +46,8 @@ oneway interface IRecentTasksListener { void onRunningTaskChanged(in RunningTaskInfo taskInfo); /** A task has moved to front. */ - oneway void onTaskMovedToFront(in RunningTaskInfo taskInfo); + void onTaskMovedToFront(in GroupedTaskInfo[] visibleTasks); /** A task info has changed. */ - oneway void onTaskInfoChanged(in RunningTaskInfo taskInfo); + void onTaskInfoChanged(in RunningTaskInfo taskInfo); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java index 8c5d1e7e069d..364a087211c5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java @@ -19,7 +19,7 @@ package com.android.wm.shell.recents; import android.annotation.Nullable; import android.graphics.Color; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.annotations.ExternalThread; import java.util.List; @@ -35,7 +35,7 @@ public interface RecentTasks { * Gets the set of recent tasks. */ default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor, - Consumer<List<GroupedRecentTaskInfo>> callback) { + Consumer<List<GroupedTaskInfo>> callback) { } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index faa20159f64a..9911669d2cb8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -24,10 +24,12 @@ import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_R import android.Manifest; import android.annotation.RequiresPermission; import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityTaskManager; import android.app.IApplicationThread; import android.app.KeyguardManager; import android.app.PendingIntent; +import android.app.TaskInfo; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -55,7 +57,7 @@ import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; @@ -379,7 +381,8 @@ public class RecentTasksController implements TaskStackListenerCallback, return; } try { - mListener.onTaskMovedToFront(taskInfo); + GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo); + mListener.onTaskMovedToFront(new GroupedTaskInfo[]{ runningTask }); } catch (RemoteException e) { Slog.w(TAG, "Failed call onTaskMovedToFront", e); } @@ -407,27 +410,27 @@ public class RecentTasksController implements TaskStackListenerCallback, } @VisibleForTesting - ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { + ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { // Note: the returned task list is from the most-recent to least-recent order - final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks( + final List<RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks( maxNum, flags, userId); // Make a mapping of task id -> task info - final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>(); + final SparseArray<TaskInfo> rawMapping = new SparseArray<>(); for (int i = 0; i < rawList.size(); i++) { - final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i); + final TaskInfo taskInfo = rawList.get(i); rawMapping.put(taskInfo.taskId, taskInfo); } - ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>(); + ArrayList<TaskInfo> freeformTasks = new ArrayList<>(); Set<Integer> minimizedFreeformTasks = new HashSet<>(); int mostRecentFreeformTaskIndex = Integer.MAX_VALUE; // Pull out the pairs as we iterate back in the list - ArrayList<GroupedRecentTaskInfo> recentTasks = new ArrayList<>(); + ArrayList<GroupedTaskInfo> recentTasks = new ArrayList<>(); for (int i = 0; i < rawList.size(); i++) { - final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i); + final RecentTaskInfo taskInfo = rawList.get(i); if (!rawMapping.contains(taskInfo.taskId)) { // If it's not in the mapping, then it was already paired with another task continue; @@ -460,20 +463,20 @@ public class RecentTasksController implements TaskStackListenerCallback, final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID); if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains( pairedTaskId)) { - final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); + final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); rawMapping.remove(pairedTaskId); - recentTasks.add(GroupedRecentTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, + recentTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, mTaskSplitBoundsMap.get(pairedTaskId))); } else { - recentTasks.add(GroupedRecentTaskInfo.forSingleTask(taskInfo)); + recentTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo)); } } // Add a special entry for freeform tasks if (!freeformTasks.isEmpty()) { recentTasks.add(mostRecentFreeformTaskIndex, - GroupedRecentTaskInfo.forFreeformTasks( - freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0]), + GroupedTaskInfo.forFreeformTasks( + freeformTasks, minimizedFreeformTasks)); } @@ -514,16 +517,16 @@ public class RecentTasksController implements TaskStackListenerCallback, * {@param ignoreTaskToken} if it is non-null. */ @Nullable - public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName, + public RecentTaskInfo findTaskInBackground(ComponentName componentName, int userId, @Nullable WindowContainerToken ignoreTaskToken) { if (componentName == null) { return null; } - List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( + List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); for (int i = 0; i < tasks.size(); i++) { - final ActivityManager.RecentTaskInfo task = tasks.get(i); + final RecentTaskInfo task = tasks.get(i); if (task.isVisible) { continue; } @@ -541,12 +544,12 @@ public class RecentTasksController implements TaskStackListenerCallback, * Find the background task that match the given taskId. */ @Nullable - public ActivityManager.RecentTaskInfo findTaskInBackground(int taskId) { - List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( + public RecentTaskInfo findTaskInBackground(int taskId) { + List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); for (int i = 0; i < tasks.size(); i++) { - final ActivityManager.RecentTaskInfo task = tasks.get(i); + final RecentTaskInfo task = tasks.get(i); if (task.isVisible) { continue; } @@ -570,7 +573,7 @@ public class RecentTasksController implements TaskStackListenerCallback, pw.println(prefix + TAG); pw.println(prefix + " mListener=" + mListener); pw.println(prefix + "Tasks:"); - ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE, + ArrayList<GroupedTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); for (int i = 0; i < recentTasks.size(); i++) { pw.println(innerPrefix + recentTasks.get(i)); @@ -584,9 +587,9 @@ public class RecentTasksController implements TaskStackListenerCallback, private class RecentTasksImpl implements RecentTasks { @Override public void getRecentTasks(int maxNum, int flags, int userId, Executor executor, - Consumer<List<GroupedRecentTaskInfo>> callback) { + Consumer<List<GroupedTaskInfo>> callback) { mMainExecutor.execute(() -> { - List<GroupedRecentTaskInfo> tasks = + List<GroupedTaskInfo> tasks = RecentTasksController.this.getRecentTasks(maxNum, flags, userId); executor.execute(() -> callback.accept(tasks)); }); @@ -650,7 +653,7 @@ public class RecentTasksController implements TaskStackListenerCallback, } @Override - public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { + public void onTaskMovedToFront(GroupedTaskInfo[] taskInfo) { mListener.call(l -> l.onTaskMovedToFront(taskInfo)); } @@ -692,17 +695,20 @@ public class RecentTasksController implements TaskStackListenerCallback, } @Override - public GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) + public GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) throws RemoteException { if (mController == null) { // The controller is already invalidated -- just return an empty task list for now - return new GroupedRecentTaskInfo[0]; + return new GroupedTaskInfo[0]; } - final GroupedRecentTaskInfo[][] out = new GroupedRecentTaskInfo[][]{null}; + final GroupedTaskInfo[][] out = new GroupedTaskInfo[][]{null}; executeRemoteCallWithTaskPermission(mController, "getRecentTasks", - (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId) - .toArray(new GroupedRecentTaskInfo[0]), + (controller) -> { + List<GroupedTaskInfo> tasks = controller.getRecentTasks( + maxNum, flags, userId); + out[0] = tasks.toArray(new GroupedTaskInfo[0]); + }, true /* blocking */); return out[0]; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index ec58292b352c..29e4b5bca5cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -395,10 +395,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { continue; } // No default animation for this, so just update bounds/position. - final int rootIdx = TransitionUtil.rootIndexFor(change, info); - startTransaction.setPosition(change.getLeash(), - change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x, - change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y); + if (change.getParent() == null) { + // For independent change without a parent, we have reparented it to the root + // leash in Transitions#setupAnimHierarchy. + final int rootIdx = TransitionUtil.rootIndexFor(change, info); + startTransaction.setPosition(change.getLeash(), + change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x, + change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y); + } else { + startTransaction.setPosition(change.getLeash(), + change.getEndRelOffset().x, change.getEndRelOffset().y); + } // Seamless display transition doesn't need to animate. if (isSeamlessDisplayChange) continue; if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index be4fd7c5eeec..7265fb8f8027 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -24,6 +24,8 @@ import static android.content.pm.PackageManager.FEATURE_PC; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; import static android.view.WindowManager.TRANSIT_CHANGE; +import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; + import android.app.ActivityManager.RunningTaskInfo; import android.content.ContentResolver; import android.content.Context; @@ -195,7 +197,12 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT return; } - decoration.relayout(taskInfo, decoration.mHasGlobalFocus); + if (enableDisplayFocusInShellTransitions()) { + // Pass the current global focus status to avoid updates outside of a ShellTransition. + decoration.relayout(taskInfo, decoration.mHasGlobalFocus); + } else { + decoration.relayout(taskInfo, taskInfo.isFocused); + } } @Override @@ -496,4 +503,4 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT return Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0; } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 17e3dd2fc68e..f2d8a782de34 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -31,6 +31,7 @@ import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowInsets.Type.statusBars; import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU; +import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing; import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR; @@ -468,7 +469,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, removeTaskFromEventReceiver(oldTaskInfo.displayId); incrementEventReceiverTasks(taskInfo.displayId); } - decoration.relayout(taskInfo, decoration.mHasGlobalFocus); + if (enableDisplayFocusInShellTransitions()) { + // Pass the current global focus status to avoid updates outside of a ShellTransition. + decoration.relayout(taskInfo, decoration.mHasGlobalFocus); + } else { + decoration.relayout(taskInfo, taskInfo.isFocused); + } mActivityOrientationChangeHandler.ifPresent(handler -> handler.handleActivityOrientationChange(oldTaskInfo, taskInfo)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt index e43c3a613157..61963cde2d06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt @@ -30,6 +30,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayChangeController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator @@ -48,6 +49,7 @@ class DesktopTilingDecorViewModel( private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val returnToDragStartAnimator: ReturnToDragStartAnimator, private val taskRepository: DesktopRepository, + private val desktopModeEventLogger: DesktopModeEventLogger, ) : DisplayChangeController.OnDisplayChangingListener { @VisibleForTesting var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>() @@ -80,6 +82,7 @@ class DesktopTilingDecorViewModel( toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, taskRepository, + desktopModeEventLogger, ) tilingTransitionHandlerByDisplayId.put(displayId, newHandler) newHandler diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt index 209eb5e501b2..6cdc517c9cb7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt @@ -23,6 +23,7 @@ import android.graphics.Rect import android.graphics.Region import android.os.Binder import android.view.LayoutInflater +import android.view.MotionEvent import android.view.RoundedCorner import android.view.SurfaceControl import android.view.SurfaceControlViewHost @@ -39,6 +40,7 @@ import android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER import android.view.WindowlessWindowManager import com.android.wm.shell.R import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger import java.util.function.Supplier /** @@ -141,8 +143,9 @@ class DesktopTilingDividerWindowManager( t.setRelativeLayer(leash, relativeLeash, 1) } - override fun onDividerMoveStart(pos: Int) { + override fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent) { setSlippery(false) + transitionHandler.onDividerHandleDragStart(motionEvent) } /** @@ -161,13 +164,13 @@ class DesktopTilingDividerWindowManager( * Notifies the transition handler of tiling operations ending, which might result in resizing * WindowContainerTransactions if the sizes of the tiled tasks changed. */ - override fun onDividerMovedEnd(pos: Int) { + override fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent) { setSlippery(true) val t = transactionSupplier.get() t.setPosition(leash, pos.toFloat() - maxRoundedCornerRadius, dividerBounds.top.toFloat()) val dividerWidth = dividerBounds.width() dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom) - transitionHandler.onDividerHandleDragEnd(dividerBounds, t) + transitionHandler.onDividerHandleDragEnd(dividerBounds, t, motionEvent) } private fun getWindowManagerParams(): WindowManager.LayoutParams { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt index c46767c3a51d..1c593c0362ba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt @@ -25,6 +25,7 @@ import android.graphics.Rect import android.os.IBinder import android.os.UserHandle import android.util.Slog +import android.view.MotionEvent import android.view.SurfaceControl import android.view.SurfaceControl.Transaction import android.view.WindowManager.TRANSIT_CHANGE @@ -44,6 +45,8 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator @@ -70,6 +73,7 @@ class DesktopTilingWindowDecoration( private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val returnToDragStartAnimator: ReturnToDragStartAnimator, private val taskRepository: DesktopRepository, + private val desktopModeEventLogger: DesktopModeEventLogger, private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }, ) : Transitions.TransitionHandler, @@ -218,6 +222,25 @@ class DesktopTilingWindowDecoration( return tilingManager } + fun onDividerHandleDragStart(motionEvent: MotionEvent) { + val leftTiledTask = leftTaskResizingHelper ?: return + val rightTiledTask = rightTaskResizingHelper ?: return + + desktopModeEventLogger.logTaskResizingStarted( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + leftTiledTask.taskInfo, + displayController + ) + + desktopModeEventLogger.logTaskResizingStarted( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + rightTiledTask.taskInfo, + displayController + ) + } + fun onDividerHandleMoved(dividerBounds: Rect, t: SurfaceControl.Transaction): Boolean { val leftTiledTask = leftTaskResizingHelper ?: return false val rightTiledTask = rightTaskResizingHelper ?: return false @@ -266,10 +289,32 @@ class DesktopTilingWindowDecoration( return true } - fun onDividerHandleDragEnd(dividerBounds: Rect, t: SurfaceControl.Transaction) { + fun onDividerHandleDragEnd( + dividerBounds: Rect, + t: SurfaceControl.Transaction, + motionEvent: MotionEvent, + ) { val leftTiledTask = leftTaskResizingHelper ?: return val rightTiledTask = rightTaskResizingHelper ?: return + desktopModeEventLogger.logTaskResizingEnded( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + leftTiledTask.taskInfo, + leftTiledTask.newBounds.height(), + leftTiledTask.newBounds.width(), + displayController + ) + + desktopModeEventLogger.logTaskResizingEnded( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + rightTiledTask.taskInfo, + rightTiledTask.newBounds.height(), + rightTiledTask.newBounds.width(), + displayController + ) + if (leftTiledTask.newBounds == leftTiledTask.bounds) { leftTiledTask.hideVeil() rightTiledTask.hideVeil() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt index b3b30ad4c09e..9799d01afc9f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt @@ -15,14 +15,16 @@ */ package com.android.wm.shell.windowdecor.tiling +import android.view.MotionEvent + /** Divider move callback to whichever entity that handles the moving logic. */ interface DividerMoveCallback { /** Called on the divider move start gesture. */ - fun onDividerMoveStart(pos: Int) + fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent) /** Called on the divider moved by dragging it. */ fun onDividerMove(pos: Int): Boolean /** Called on divider move gesture end. */ - fun onDividerMovedEnd(pos: Int) + fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt index 89229051941c..111e28e450bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt @@ -206,7 +206,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { if (!isWithinHandleRegion(yTouchPosInDivider)) return true - callback.onDividerMoveStart(touchPos) + callback.onDividerMoveStart(touchPos, event) setTouching() canResize = true } @@ -230,7 +230,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion if (!canResize) return true if (moving && resized) { dividerBounds.left = dividerBounds.left + lastAcceptedPos - startPos - callback.onDividerMovedEnd(dividerBounds.left) + callback.onDividerMovedEnd(dividerBounds.left, event) } moving = false canResize = false diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt index df061e368071..b06c2dad4ffc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt @@ -447,6 +447,37 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) + fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() { + val wct = WindowContainerTransaction() + val launchingTask = createTask(WINDOWING_MODE_FREEFORM) + val nonLaunchTaskChange = createChange(createTask(WINDOWING_MODE_FREEFORM)) + val transition = Binder() + whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull())) + .thenReturn(transition) + mixedHandler.addPendingMixedTransition( + PendingMixedTransition.Launch( + transition = transition, + launchingTask = launchingTask.taskId, + minimizingTask = null, + exitingImmersiveTask = null, + ) + ) + + val started = mixedHandler.startAnimation( + transition, + createTransitionInfo( + TRANSIT_OPEN, + listOf(nonLaunchTaskChange) + ), + SurfaceControl.Transaction(), + SurfaceControl.Transaction(), + ) { } + + assertFalse("Should not start animation without launching desktop task", started) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java index e19a10a78417..b816f0ef041e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java @@ -93,6 +93,17 @@ public class PipExpandAnimatorTest { .thenReturn(mMockTransaction); when(mMockTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat())) .thenReturn(mMockTransaction); + // No-op on the mMockStartTransaction + when(mMockStartTransaction.setAlpha(any(SurfaceControl.class), anyFloat())) + .thenReturn(mMockFinishTransaction); + when(mMockStartTransaction.setCrop(any(SurfaceControl.class), any(Rect.class))) + .thenReturn(mMockFinishTransaction); + when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) + .thenReturn(mMockFinishTransaction); + when(mMockStartTransaction.setCornerRadius(any(SurfaceControl.class), anyFloat())) + .thenReturn(mMockFinishTransaction); + when(mMockStartTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat())) + .thenReturn(mMockFinishTransaction); // Do the same for mMockFinishTransaction when(mMockFinishTransaction.setAlpha(any(SurfaceControl.class), anyFloat())) .thenReturn(mMockFinishTransaction); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java new file mode 100644 index 000000000000..89cb729d17b5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip2.phone; + +import static com.android.wm.shell.pip2.phone.PipTaskListener.ANIMATING_ASPECT_RATIO_CHANGE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; +import static org.mockito.kotlin.MatchersKt.eq; +import static org.mockito.kotlin.VerificationKt.clearInvocations; +import static org.mockito.kotlin.VerificationKt.times; +import static org.mockito.kotlin.VerificationKt.verify; +import static org.mockito.kotlin.VerificationKt.verifyZeroInteractions; + +import android.app.ActivityManager; +import android.app.PendingIntent; +import android.app.PictureInPictureParams; +import android.app.RemoteAction; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.Rational; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.pip2.animation.PipResizeAnimator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit test against {@link PipTaskListener}. + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner.class) +public class PipTaskListenerTest { + + @Mock private Context mMockContext; + @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; + @Mock private PipTransitionState mMockPipTransitionState; + @Mock private SurfaceControl mMockLeash; + @Mock private PipScheduler mMockPipScheduler; + @Mock private PipBoundsState mMockPipBoundsState; + @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm; + @Mock private ShellExecutor mMockShellExecutor; + + @Mock private Icon mMockIcon; + @Mock private PendingIntent mMockPendingIntent; + + @Mock private PipTaskListener.PipParamsChangedCallback mMockPipParamsChangedCallback; + + @Mock private PipResizeAnimator mMockPipResizeAnimator; + + private ArgumentCaptor<List<RemoteAction>> mRemoteActionListCaptor; + + private PipTaskListener mPipTaskListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mRemoteActionListCaptor = ArgumentCaptor.forClass(List.class); + when(mMockPipTransitionState.getPinnedTaskLeash()).thenReturn(mMockLeash); + } + + @Test + public void constructor_addPipTransitionStateChangedListener() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + + verify(mMockPipTransitionState).addPipTransitionStateChangedListener(eq(mPipTaskListener)); + } + + @Test + public void setPictureInPictureParams_updatePictureInPictureParams() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + PictureInPictureParams params = mPipTaskListener.getPictureInPictureParams(); + assertEquals(aspectRatio, params.getAspectRatio()); + assertTrue(params.hasSetActions()); + assertEquals(1, params.getActions().size()); + assertEquals(action1, params.getActions().get(0).getTitle()); + } + + @Test + public void setPictureInPictureParams_withActionsChanged_callbackActionsChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + action1 = "modified action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + verify(mMockPipParamsChangedCallback).onActionsChanged( + mRemoteActionListCaptor.capture(), any()); + assertEquals(1, mRemoteActionListCaptor.getValue().size()); + assertEquals(action1, mRemoteActionListCaptor.getValue().get(0).getTitle()); + } + + @Test + public void setPictureInPictureParams_withoutActionsChanged_doesNotCallbackActionsChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + verifyZeroInteractions(mMockPipParamsChangedCallback); + } + + @Test + public void onTaskInfoChanged_withActionsChanged_callbackActionsChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat()); + String action1 = "action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + clearInvocations(mMockPipBoundsState); + action1 = "modified action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + verify(mMockPipTransitionState, times(0)) + .setOnIdlePipTransitionStateRunnable(any(Runnable.class)); + verify(mMockPipParamsChangedCallback).onActionsChanged( + mRemoteActionListCaptor.capture(), any()); + assertEquals(1, mRemoteActionListCaptor.getValue().size()); + assertEquals(action1, mRemoteActionListCaptor.getValue().get(0).getTitle()); + } + + @Test + public void onTaskInfoChanged_withAspectRatioChanged_callbackAspectRatioChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat()); + String action1 = "action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + clearInvocations(mMockPipBoundsState); + aspectRatio = new Rational(16, 9); + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + verify(mMockPipTransitionState).setOnIdlePipTransitionStateRunnable(any(Runnable.class)); + verifyZeroInteractions(mMockPipParamsChangedCallback); + } + + @Test + public void onTaskInfoChanged_withoutParamsChanged_doesNotCallbackAspectRatioChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat()); + String action1 = "action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + verifyZeroInteractions(mMockPipParamsChangedCallback); + verify(mMockPipTransitionState, times(0)) + .setOnIdlePipTransitionStateRunnable(any(Runnable.class)); + } + + @Test + public void onPipTransitionStateChanged_scheduledBoundsChangeWithAspectRatioChange_schedule() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extras); + + verify(mMockPipScheduler).scheduleAnimateResizePip(any(), anyBoolean(), anyInt()); + } + + @Test + public void onPipTransitionStateChanged_scheduledBoundsChangeWithoutAspectRatioChange_noop() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, false); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + extras); + + verifyZeroInteractions(mMockPipScheduler); + } + + @Test + public void onPipTransitionStateChanged_changingPipBoundsWaitAspectRatioChange_animate() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true); + extras.putParcelable(PipTransition.PIP_DESTINATION_BOUNDS, + new Rect(0, 0, 100, 100)); + when(mMockPipBoundsState.getBounds()).thenReturn(new Rect(0, 0, 200, 200)); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + extras); + mPipTaskListener.setPipResizeAnimatorSupplier( + (context, leash, startTx, finishTx, baseBounds, startBounds, endBounds, + duration, delta) -> mMockPipResizeAnimator); + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + PipTransitionState.CHANGING_PIP_BOUNDS, + extras); + + verify(mMockPipResizeAnimator, times(1)).start(); + } + + @Test + public void onPipTransitionStateChanged_changingPipBoundsNotAspectRatioChange_noop() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, false); + extras.putParcelable(PipTransition.PIP_DESTINATION_BOUNDS, + new Rect(0, 0, 100, 100)); + when(mMockPipBoundsState.getBounds()).thenReturn(new Rect(0, 0, 200, 200)); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + extras); + mPipTaskListener.setPipResizeAnimatorSupplier( + (context, leash, startTx, finishTx, baseBounds, startBounds, endBounds, + duration, delta) -> mMockPipResizeAnimator); + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + PipTransitionState.CHANGING_PIP_BOUNDS, + extras); + + verify(mMockPipResizeAnimator, times(0)).start(); + } + + private PictureInPictureParams getPictureInPictureParams(Rational aspectRatio, + String... actions) { + final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder(); + builder.setAspectRatio(aspectRatio); + final List<RemoteAction> remoteActions = new ArrayList<>(); + for (String action : actions) { + remoteActions.add(new RemoteAction(mMockIcon, action, action, mMockPendingIntent)); + } + if (!remoteActions.isEmpty()) { + builder.setActions(remoteActions); + } + return builder.build(); + } + + private ActivityManager.RunningTaskInfo getTaskInfo(Rational aspectRatio, + String... actions) { + final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.pictureInPictureParams = getPictureInPictureParams(aspectRatio, actions); + return taskInfo; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt index 0c100fca2036..2b30bc360d06 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.recents import android.app.ActivityManager +import android.app.TaskInfo import android.graphics.Rect import android.os.Parcel import android.testing.AndroidTestingRunner @@ -24,11 +25,10 @@ import android.window.IWindowContainerToken import android.window.WindowContainerToken import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.shared.GroupedRecentTaskInfo -import com.android.wm.shell.shared.GroupedRecentTaskInfo.CREATOR -import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM -import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE -import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT +import com.android.wm.shell.shared.GroupedTaskInfo +import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM +import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FULLSCREEN +import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT import com.android.wm.shell.shared.split.SplitBounds import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 import com.google.common.truth.Correspondence @@ -39,15 +39,15 @@ import org.junit.runner.RunWith import org.mockito.Mockito.mock /** - * Tests for [GroupedRecentTaskInfo] + * Tests for [GroupedTaskInfo] */ @SmallTest @RunWith(AndroidTestingRunner::class) -class GroupedRecentTaskInfoTest : ShellTestCase() { +class GroupedTaskInfoTest : ShellTestCase() { @Test fun testSingleTask_hasCorrectType() { - assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_SINGLE) + assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_FULLSCREEN) } @Test @@ -117,8 +117,9 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { recentTaskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) - assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SINGLE) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) + assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FULLSCREEN) assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1) assertThat(recentTaskInfoParcel.taskInfo2).isNull() } @@ -130,7 +131,8 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { recentTaskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SPLIT) assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1) assertThat(recentTaskInfoParcel.taskInfo2).isNotNull() @@ -146,11 +148,12 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { recentTaskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM) assertThat(recentTaskInfoParcel.taskInfoList).hasSize(3) // Only compare task ids - val taskIdComparator = Correspondence.transforming<ActivityManager.RecentTaskInfo, Int>( + val taskIdComparator = Correspondence.transforming<TaskInfo, Int>( { it?.taskId }, "has taskId of" ) assertThat(recentTaskInfoParcel.taskInfoList).comparingElementsUsing(taskIdComparator) @@ -167,7 +170,8 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM) assertThat(recentTaskInfoParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray()) } @@ -177,24 +181,24 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { token = WindowContainerToken(mock(IWindowContainerToken::class.java)) } - private fun singleTaskGroupInfo(): GroupedRecentTaskInfo { + private fun singleTaskGroupInfo(): GroupedTaskInfo { val task = createTaskInfo(id = 1) - return GroupedRecentTaskInfo.forSingleTask(task) + return GroupedTaskInfo.forFullscreenTasks(task) } - private fun splitTasksGroupInfo(): GroupedRecentTaskInfo { + private fun splitTasksGroupInfo(): GroupedTaskInfo { val task1 = createTaskInfo(id = 1) val task2 = createTaskInfo(id = 2) val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50) - return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds) + return GroupedTaskInfo.forSplitTasks(task1, task2, splitBounds) } private fun freeformTasksGroupInfo( freeformTaskIds: Array<Int>, minimizedTaskIds: Array<Int> = emptyArray() - ): GroupedRecentTaskInfo { - return GroupedRecentTaskInfo.forFreeformTasks( - freeformTaskIds.map { createTaskInfo(it) }.toTypedArray(), + ): GroupedTaskInfo { + return GroupedTaskInfo.forFreeformTasks( + freeformTaskIds.map { createTaskInfo(it) }.toList(), minimizedTaskIds.toSet()) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 9b73d53e0639..dede583ca970 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.when; import static java.lang.Integer.MAX_VALUE; import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityTaskManager; import android.app.KeyguardManager; import android.content.ComponentName; @@ -71,7 +72,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopRepository; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.ShellSharedConstants; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.split.SplitBounds; @@ -193,8 +194,8 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testAddRemoveSplitNotifyChange() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); setRawList(t1, t2); mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(SplitBounds.class)); @@ -207,8 +208,8 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testAddSameSplitBoundsInfoSkipNotifyChange() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); setRawList(t1, t2); // Verify only one update if the split info is the same @@ -223,13 +224,13 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); setRawList(t1, t2, t3); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t2.taskId, -1, @@ -238,12 +239,12 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_withPairs() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); - ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t6 = makeTaskInfo(6); setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] @@ -255,8 +256,8 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds); mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t2.taskId, t4.taskId, @@ -267,14 +268,14 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_ReturnsRecentTasksAsynchronously() { @SuppressWarnings("unchecked") - final List<GroupedRecentTaskInfo>[] recentTasks = new List[1]; - Consumer<List<GroupedRecentTaskInfo>> consumer = argument -> recentTasks[0] = argument; - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); - ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6); + final List<GroupedTaskInfo>[] recentTasks = new List[1]; + Consumer<List<GroupedTaskInfo>> consumer = argument -> recentTasks[0] = argument; + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t6 = makeTaskInfo(6); setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] @@ -287,7 +288,8 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); mRecentTasksController.asRecentTasks() - .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, consumer); + .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, + consumer); mMainExecutor.flushAll(); assertGroupedTasksListEquals(recentTasks[0], @@ -299,28 +301,28 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); when(mDesktopRepository.isActiveTask(1)).thenReturn(true); when(mDesktopRepository.isActiveTask(3)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // 2 freeform tasks should be grouped into one, 3 total recents entries assertEquals(3, recentTasks.size()); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); - GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1); - GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2); + GroupedTaskInfo freeformGroup = recentTasks.get(0); + GroupedTaskInfo singleGroup1 = recentTasks.get(1); + GroupedTaskInfo singleGroup2 = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType()); + assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType()); // Check freeform group entries assertEquals(t1, freeformGroup.getTaskInfoList().get(0)); @@ -333,11 +335,11 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_freeformTaskOrder() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); setRawList(t1, t2, t3, t4, t5); SplitBounds pair1Bounds = @@ -347,19 +349,19 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mDesktopRepository.isActiveTask(3)).thenReturn(true); when(mDesktopRepository.isActiveTask(5)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries assertEquals(3, recentTasks.size()); - GroupedRecentTaskInfo splitGroup = recentTasks.get(0); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(1); - GroupedRecentTaskInfo singleGroup = recentTasks.get(2); + GroupedTaskInfo splitGroup = recentTasks.get(0); + GroupedTaskInfo freeformGroup = recentTasks.get(1); + GroupedTaskInfo singleGroup = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedRecentTaskInfo.TYPE_SPLIT, splitGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_SPLIT, splitGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup.getType()); // Check freeform group entries assertEquals(t3, freeformGroup.getTaskInfoList().get(0)); @@ -378,24 +380,24 @@ public class RecentTasksControllerTest extends ShellTestCase { ExtendedMockito.doReturn(false) .when(() -> DesktopModeStatus.canEnterDesktopMode(any())); - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); when(mDesktopRepository.isActiveTask(1)).thenReturn(true); when(mDesktopRepository.isActiveTask(3)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // Expect no grouping of tasks assertEquals(4, recentTasks.size()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(0).getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(1).getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(2).getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(3).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(0).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(1).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(2).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(3).getType()); assertEquals(t1, recentTasks.get(0).getTaskInfo1()); assertEquals(t2, recentTasks.get(1).getTaskInfo1()); @@ -405,11 +407,11 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_proto2Enabled_includesMinimizedFreeformTasks() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); setRawList(t1, t2, t3, t4, t5); when(mDesktopRepository.isActiveTask(1)).thenReturn(true); @@ -417,19 +419,19 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mDesktopRepository.isActiveTask(5)).thenReturn(true); when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries assertEquals(3, recentTasks.size()); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); - GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1); - GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2); + GroupedTaskInfo freeformGroup = recentTasks.get(0); + GroupedTaskInfo singleGroup1 = recentTasks.get(1); + GroupedTaskInfo singleGroup2 = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType()); + assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType()); // Check freeform group entries assertEquals(3, freeformGroup.getTaskInfoList().size()); @@ -445,8 +447,8 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) public void testGetRecentTasks_hasDesktopTasks_persistenceEnabled_freeformTaskHaveBoundsSet() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); t1.lastNonFullscreenBounds = new Rect(100, 200, 300, 400); t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450); @@ -455,11 +457,11 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mDesktopRepository.isActiveTask(1)).thenReturn(true); when(mDesktopRepository.isActiveTask(2)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); assertEquals(1, recentTasks.size()); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); + GroupedTaskInfo freeformGroup = recentTasks.get(0); // Check bounds assertEquals(t1.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get( @@ -478,9 +480,9 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testRemovedTaskRemovesSplit() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); setRawList(t1, t2, t3); // Add a pair @@ -500,7 +502,7 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testTaskWindowingModeChangedNotifiesChange() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t1 = makeTaskInfo(1); setRawList(t1); // Remove one of the tasks and ensure the pair is removed @@ -607,7 +609,8 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo); - verify(mRecentTasksListener).onTaskMovedToFront(taskInfo); + GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo); + verify(mRecentTasksListener).onTaskMovedToFront(eq(new GroupedTaskInfo[] { runningTask })); } @Test @@ -656,8 +659,8 @@ public class RecentTasksControllerTest extends ShellTestCase { /** * Helper to create a task with a given task id. */ - private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) { - ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo(); + private RecentTaskInfo makeTaskInfo(int taskId) { + RecentTaskInfo info = new RecentTaskInfo(); info.taskId = taskId; info.lastNonFullscreenBounds = new Rect(); return info; @@ -676,10 +679,10 @@ public class RecentTasksControllerTest extends ShellTestCase { /** * Helper to set the raw task list on the controller. */ - private ArrayList<ActivityManager.RecentTaskInfo> setRawList( - ActivityManager.RecentTaskInfo... tasks) { - ArrayList<ActivityManager.RecentTaskInfo> rawList = new ArrayList<>(); - for (ActivityManager.RecentTaskInfo task : tasks) { + private ArrayList<RecentTaskInfo> setRawList( + RecentTaskInfo... tasks) { + ArrayList<RecentTaskInfo> rawList = new ArrayList<>(); + for (RecentTaskInfo task : tasks) { rawList.add(task); } doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(), @@ -693,11 +696,11 @@ public class RecentTasksControllerTest extends ShellTestCase { * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in * the grouped task list */ - private void assertGroupedTasksListEquals(List<GroupedRecentTaskInfo> recentTasks, + private void assertGroupedTasksListEquals(List<GroupedTaskInfo> recentTasks, int... expectedTaskIds) { int[] flattenedTaskIds = new int[recentTasks.size() * 2]; for (int i = 0; i < recentTasks.size(); i++) { - GroupedRecentTaskInfo pair = recentTasks.get(i); + GroupedTaskInfo pair = recentTasks.get(i); int taskId1 = pair.getTaskInfo1().taskId; flattenedTaskIds[2 * i] = taskId1; flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index ef3af8e7bdac..966651f19711 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -217,7 +217,6 @@ public class SplitScreenControllerTests extends ShellTestCase { // Put the same component to the top running task ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); - doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, @@ -238,7 +237,6 @@ public class SplitScreenControllerTests extends ShellTestCase { // Put the same component to the top running task ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); - doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any()); // Put the same component into a task in the background ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 56267174ba75..956100d9bc03 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -1307,6 +1307,48 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { verify(decor).closeMaximizeMenu() } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS) + fun testOnTaskInfoChanged_enableShellTransitionsFlag() { + val task = createTask( + windowingMode = WINDOWING_MODE_FREEFORM + ) + val taskSurface = SurfaceControl() + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task, taskSurface) + assertTrue(windowDecorByTaskIdSpy.contains(task.taskId)) + + decoration.mHasGlobalFocus = true + desktopModeWindowDecorViewModel.onTaskInfoChanged(task) + verify(decoration).relayout(task, true) + + decoration.mHasGlobalFocus = false + desktopModeWindowDecorViewModel.onTaskInfoChanged(task) + verify(decoration).relayout(task, false) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS) + fun testOnTaskInfoChanged_disableShellTransitionsFlag() { + val task = createTask( + windowingMode = WINDOWING_MODE_FREEFORM + ) + val taskSurface = SurfaceControl() + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task, taskSurface) + assertTrue(windowDecorByTaskIdSpy.contains(task.taskId)) + + task.isFocused = true + desktopModeWindowDecorViewModel.onTaskInfoChanged(task) + verify(decoration).relayout(task, true) + + task.isFocused = false + desktopModeWindowDecorViewModel.onTaskInfoChanged(task) + verify(decoration).relayout(task, false) + } + private fun createOpenTaskDecoration( @WindowingMode windowingMode: Int, taskSurface: SurfaceControl = SurfaceControl(), diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt index 80ad1df44a1b..d44c01592ff7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt @@ -25,6 +25,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator @@ -52,6 +53,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { private val transitionsMock: Transitions = mock() private val shellTaskOrganizerMock: ShellTaskOrganizer = mock() private val desktopRepository: DesktopRepository = mock() + private val desktopModeEventLogger: DesktopModeEventLogger = mock() private val toggleResizeDesktopTaskTransitionHandlerMock: ToggleResizeDesktopTaskTransitionHandler = mock() @@ -74,6 +76,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { toggleResizeDesktopTaskTransitionHandlerMock, returnToDragStartAnimatorMock, desktopRepository, + desktopModeEventLogger, ) whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt index f371f5223419..057d8fa3adb0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt @@ -21,6 +21,7 @@ import android.content.res.Resources import android.graphics.Rect import android.os.IBinder import android.testing.AndroidTestingRunner +import android.view.MotionEvent import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_TO_FRONT @@ -33,6 +34,8 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask @@ -91,7 +94,9 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { private val info: TransitionInfo = mock() private val finishCallback: Transitions.TransitionFinishCallback = mock() private val desktopRepository: DesktopRepository = mock() + private val desktopModeEventLogger: DesktopModeEventLogger = mock() private val desktopTilingDividerWindowManager: DesktopTilingDividerWindowManager = mock() + private val motionEvent: MotionEvent = mock() private lateinit var tilingDecoration: DesktopTilingWindowDecoration private val split_divider_width = 10 @@ -112,6 +117,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, desktopRepository, + desktopModeEventLogger, ) whenever(context.createContextAsUser(any(), any())).thenReturn(context) } @@ -371,13 +377,13 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { // End moving, no startTransition because bounds did not change. tiledTaskHelper.newBounds.set(BOUNDS) - tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction) + tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent) verify(tiledTaskHelper, times(2)).hideVeil() verify(transitions, never()).startTransition(any(), any(), any()) // Move then end again with bounds changing to ensure startTransition is called. tilingDecoration.onDividerHandleMoved(BOUNDS, transaction) - tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction) + tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent) verify(transitions, times(1)) .startTransition(eq(TRANSIT_CHANGE), any(), eq(tilingDecoration)) // No hide veil until start animation is called. @@ -389,6 +395,64 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { } @Test + fun tiledTasksResizedUsingDividerHandle_shouldLogResizingEvents() { + // Setup + val task1 = createFreeformTask() + val task2 = createFreeformTask() + val stableBounds = STABLE_BOUNDS_MOCK + whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout) + whenever(displayLayout.getStableBounds(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(stableBounds) + } + whenever(context.resources).thenReturn(resources) + whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width) + desktopWindowDecoration.mTaskInfo = task1 + task1.minWidth = 0 + task1.minHeight = 0 + initTiledTaskHelperMock(task1) + desktopWindowDecoration.mDecorWindowContext = context + whenever(resources.getBoolean(any())).thenReturn(true) + + // Act + tilingDecoration.onAppTiled( + task1, + desktopWindowDecoration, + DesktopTasksController.SnapPosition.RIGHT, + BOUNDS, + ) + tilingDecoration.onAppTiled( + task2, + desktopWindowDecoration, + DesktopTasksController.SnapPosition.LEFT, + BOUNDS, + ) + tilingDecoration.leftTaskResizingHelper = tiledTaskHelper + tilingDecoration.rightTaskResizingHelper = tiledTaskHelper + tilingDecoration.onDividerHandleDragStart(motionEvent) + // Log start event for task1 and task2, but the tasks are the same in + // this test, so we verify the same log twice. + verify(desktopModeEventLogger, times(2)).logTaskResizingStarted( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + task1, + displayController, + ) + + tilingDecoration.onDividerHandleMoved(BOUNDS, transaction) + tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent) + // Log end event for task1 and task2, but the tasks are the same in + // this test, so we verify the same log twice. + verify(desktopModeEventLogger, times(2)).logTaskResizingEnded( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + task1, + BOUNDS.height(), + BOUNDS.width(), + displayController, + ) + } + + @Test fun taskTiled_shouldBeRemoved_whenTileBroken() { val task1 = createFreeformTask() val stableBounds = STABLE_BOUNDS_MOCK diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt index c96ce955f217..734815cdd915 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt @@ -68,7 +68,7 @@ class TilingDividerViewTest : ShellTestCase() { val downMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, downMotionEvent) - verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any()) + verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any()) whenever(dividerMoveCallbackMock.onDividerMove(any())).thenReturn(true) val motionEvent = @@ -79,7 +79,7 @@ class TilingDividerViewTest : ShellTestCase() { val upMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, upMotionEvent) - verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any()) + verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any(), any()) } @Test @@ -92,12 +92,12 @@ class TilingDividerViewTest : ShellTestCase() { val downMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, downMotionEvent) - verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any()) + verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any()) val upMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, upMotionEvent) - verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any()) + verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any(), any()) } private fun getMotionEvent(eventTime: Long, action: Int, x: Float, y: Float): MotionEvent { diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index b71abdc011c1..fcb7efc35c94 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -580,6 +580,7 @@ cc_defaults { "utils/Color.cpp", "utils/LinearAllocator.cpp", "utils/StringUtils.cpp", + "utils/StatsUtils.cpp", "utils/TypefaceUtils.cpp", "utils/VectorDrawableUtils.cpp", "AnimationContext.cpp", diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index e074a27db38f..a9a5db8181ba 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -27,8 +27,8 @@ #include <SkColorSpace.h> #include <SkColorType.h> #include <SkEncodedOrigin.h> -#include <SkImageInfo.h> #include <SkGainmapInfo.h> +#include <SkImageInfo.h> #include <SkMatrix.h> #include <SkPaint.h> #include <SkPngChunkReader.h> @@ -43,6 +43,8 @@ #include <memory> +#include "modules/skcms/src/skcms_public.h" + using namespace android; sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const { diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index 9cd6e253140e..e5fb75575ac3 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -49,6 +49,7 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, minikinPaint.fontStyle = resolvedFace->fStyle; minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings(); minikinPaint.fontVariationSettings = paint->getFontVariationOverride(); + minikinPaint.verticalText = paint->isVerticalText(); const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant(); if (familyVariant.has_value()) { diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index 7eb849fe6e3d..594ea31387ad 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -158,6 +158,7 @@ public: SkSamplingOptions sampling() const { return SkSamplingOptions(this->filterMode()); } + bool isVerticalText() const { return mVerticalText; } void setVariationOverride(minikin::VariationSettings&& varSettings) { mFontVariationOverride = std::move(varSettings); @@ -202,6 +203,7 @@ private: bool mUnderline = false; bool mDevKern = false; minikin::RunFlag mRunFlag = minikin::RunFlag::NONE; + bool mVerticalText = false; }; } // namespace android diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index 6dfcedc3d918..fa5325d90218 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -49,7 +49,8 @@ Paint::Paint(const Paint& paint) , mStrikeThru(paint.mStrikeThru) , mUnderline(paint.mUnderline) , mDevKern(paint.mDevKern) - , mRunFlag(paint.mRunFlag) {} + , mRunFlag(paint.mRunFlag) + , mVerticalText(paint.mVerticalText) {} Paint::~Paint() {} @@ -71,6 +72,7 @@ Paint& Paint::operator=(const Paint& other) { mUnderline = other.mUnderline; mDevKern = other.mDevKern; mRunFlag = other.mRunFlag; + mVerticalText = other.mVerticalText; return *this; } @@ -83,7 +85,8 @@ bool operator==(const Paint& a, const Paint& b) { a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit && a.mTypeface == b.mTypeface && a.mAlign == b.mAlign && a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru && - a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag; + a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag && + a.mVerticalText == b.mVerticalText; } void Paint::reset() { @@ -97,6 +100,7 @@ void Paint::reset() { mStrikeThru = false; mUnderline = false; mDevKern = false; + mVerticalText = false; mRunFlag = minikin::RunFlag::NONE; } @@ -135,6 +139,7 @@ static const uint32_t sForceAutoHinting = 0x800; // flags related to minikin::Paint static const uint32_t sUnderlineFlag = 0x08; static const uint32_t sStrikeThruFlag = 0x10; +static const uint32_t sVerticalTextFlag = 0x1000; static const uint32_t sTextRunLeftEdge = 0x2000; static const uint32_t sTextRunRightEdge = 0x4000; // flags no longer supported on native side (but mirrored for compatibility) @@ -190,6 +195,7 @@ uint32_t Paint::getJavaFlags() const { flags |= -(int)mUnderline & sUnderlineFlag; flags |= -(int)mDevKern & sDevKernFlag; flags |= -(int)mFilterBitmap & sFilterBitmapFlag; + flags |= -(int)mVerticalText & sVerticalTextFlag; if (mRunFlag & minikin::RunFlag::LEFT_EDGE) { flags |= sTextRunLeftEdge; } @@ -206,6 +212,7 @@ void Paint::setJavaFlags(uint32_t flags) { mUnderline = (flags & sUnderlineFlag) != 0; mDevKern = (flags & sDevKernFlag) != 0; mFilterBitmap = (flags & sFilterBitmapFlag) != 0; + mVerticalText = (flags & sVerticalTextFlag) != 0; std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE; if (flags & sTextRunLeftEdge) { diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp index 49a7f73fb3a3..8b43f1db84af 100644 --- a/libs/hwui/jni/BitmapFactory.cpp +++ b/libs/hwui/jni/BitmapFactory.cpp @@ -10,6 +10,7 @@ #include <stdint.h> #include <stdio.h> #include <sys/stat.h> +#include <utils/StatsUtils.h> #include <memory> @@ -630,6 +631,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, } bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied); outputBitmap.notifyPixelsChanged(); + uirenderer::logBitmapDecode(*reuseBitmap); // If a java bitmap was passed in for reuse, pass it back return javaBitmap; } @@ -650,6 +652,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, } } + uirenderer::logBitmapDecode(*hardwareBitmap); return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); } @@ -659,6 +662,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, heapBitmap->setGainmap(std::move(gainmap)); } + uirenderer::logBitmapDecode(*heapBitmap); // now create the java bitmap return bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp index f7e8e073a272..5ffd5b9016d8 100644 --- a/libs/hwui/jni/BitmapRegionDecoder.cpp +++ b/libs/hwui/jni/BitmapRegionDecoder.cpp @@ -19,6 +19,7 @@ #include <HardwareBitmapUploader.h> #include <androidfw/Asset.h> #include <sys/stat.h> +#include <utils/StatsUtils.h> #include <memory> @@ -376,6 +377,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in recycledBitmap->setGainmap(std::move(gainmap)); } bitmap::reinitBitmap(env, javaBitmap, recycledBitmap->info(), !requireUnpremul); + uirenderer::logBitmapDecode(*recycledBitmap); return javaBitmap; } @@ -392,12 +394,14 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in hardwareBitmap->setGainmap(std::move(gm)); } } + uirenderer::logBitmapDecode(*hardwareBitmap); return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags); } Bitmap* heapBitmap = heapAlloc.getStorageObjAndReset(); if (hasGainmap && heapBitmap != nullptr) { heapBitmap->setGainmap(std::move(gainmap)); } + uirenderer::logBitmapDecode(*heapBitmap); return android::bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags); } diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp index aebc4db37898..90fd3d87cba7 100644 --- a/libs/hwui/jni/ImageDecoder.cpp +++ b/libs/hwui/jni/ImageDecoder.cpp @@ -37,6 +37,7 @@ #include <hwui/Bitmap.h> #include <hwui/ImageDecoder.h> #include <sys/stat.h> +#include <utils/StatsUtils.h> #include "Bitmap.h" #include "BitmapFactory.h" @@ -485,6 +486,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong hwBitmap->setGainmap(std::move(gm)); } } + uirenderer::logBitmapDecode(*hwBitmap); return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets); } @@ -498,6 +500,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativeBitmap->setImmutable(); } + + uirenderer::logBitmapDecode(*nativeBitmap); return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets); } diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt index 2414299321a9..b5591941453d 100644 --- a/libs/hwui/libhwui.map.txt +++ b/libs/hwui/libhwui.map.txt @@ -67,6 +67,7 @@ LIBHWUI_PLATFORM { SkFILEStream::SkFILEStream*; SkImageInfo::*; SkMemoryStream::SkMemoryStream*; + android::uirenderer::logBitmapDecode*; }; local: *; diff --git a/libs/hwui/utils/StatsUtils.cpp b/libs/hwui/utils/StatsUtils.cpp new file mode 100644 index 000000000000..5c4027e1a846 --- /dev/null +++ b/libs/hwui/utils/StatsUtils.cpp @@ -0,0 +1,102 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef __ANDROID__ +#include <dlfcn.h> +#include <log/log.h> +#include <statslog_hwui.h> +#include <statssocket_lazy.h> +#include <utils/Errors.h> + +#include <mutex> +#endif + +#include <unistd.h> + +#include "StatsUtils.h" + +namespace android { +namespace uirenderer { + +#ifdef __ANDROID__ + +namespace { + +int32_t toStatsColorSpaceTransfer(skcms_TFType transferType) { + switch (transferType) { + case skcms_TFType_sRGBish: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_SRGBISH; + case skcms_TFType_PQish: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_PQISH; + case skcms_TFType_HLGish: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_HLGISH; + default: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_UNKNOWN; + } +} + +int32_t toStatsBitmapFormat(SkColorType type) { + switch (type) { + case kAlpha_8_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_A_8; + case kRGB_565_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGB_565; + case kN32_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_ARGB_8888; + case kRGBA_F16_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_F16; + case kRGBA_1010102_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_1010102; + default: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_UNKNOWN; + } +} + +} // namespace + +#endif + +void logBitmapDecode(const SkImageInfo& info, bool hasGainmap) { +#ifdef __ANDROID__ + + if (!statssocket::lazy::IsAvailable()) { + std::once_flag once; + std::call_once(once, []() { ALOGD("libstatssocket not available, dropping stats"); }); + return; + } + + skcms_TFType tfnType = skcms_TFType_Invalid; + + if (info.colorSpace()) { + skcms_TransferFunction tfn; + info.colorSpace()->transferFn(&tfn); + tfnType = skcms_TransferFunction_getType(&tfn); + } + + auto status = + stats::stats_write(uirenderer::stats::IMAGE_DECODED, static_cast<int32_t>(getuid()), + uirenderer::toStatsColorSpaceTransfer(tfnType), hasGainmap, + uirenderer::toStatsBitmapFormat(info.colorType())); + ALOGW_IF(status != OK, "Image decoding logging dropped!"); +#endif +} + +void logBitmapDecode(const Bitmap& bitmap) { + logBitmapDecode(bitmap.info(), bitmap.hasGainmap()); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/StatsUtils.h b/libs/hwui/utils/StatsUtils.h new file mode 100644 index 000000000000..0c247014a8eb --- /dev/null +++ b/libs/hwui/utils/StatsUtils.h @@ -0,0 +1,33 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <SkBitmap.h> +#include <SkColorSpace.h> +#include <SkColorType.h> +#include <cutils/compiler.h> +#include <hwui/Bitmap.h> + +namespace android { +namespace uirenderer { + +ANDROID_API void logBitmapDecode(const SkImageInfo& info, bool hasGainmap); + +ANDROID_API void logBitmapDecode(const Bitmap& bitmap); + +} // namespace uirenderer +} // namespace android diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java index 55c8ed5debf8..530d48d3e60b 100644 --- a/media/java/android/media/ImageReader.java +++ b/media/java/android/media/ImageReader.java @@ -40,6 +40,8 @@ import android.os.ParcelFileDescriptor; import android.os.Trace; import android.view.Surface; +import com.android.internal.camera.flags.Flags; + import dalvik.system.VMRuntime; import java.io.IOException; @@ -1208,6 +1210,11 @@ public class ImageReader implements AutoCloseable { default: width = nativeGetWidth(); } + if (Flags.cameraHeifGainmap()) { + if (getFormat() == ImageFormat.HEIC_ULTRAHDR){ + width = ImageReader.this.getWidth(); + } + } return width; } @@ -1227,6 +1234,11 @@ public class ImageReader implements AutoCloseable { default: height = nativeGetHeight(); } + if (Flags.cameraHeifGainmap()) { + if (getFormat() == ImageFormat.HEIC_ULTRAHDR){ + height = ImageReader.this.getHeight(); + } + } return height; } diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java index f4caad727407..c7678067f0b5 100644 --- a/media/java/android/media/ImageUtils.java +++ b/media/java/android/media/ImageUtils.java @@ -23,6 +23,8 @@ import android.media.Image.Plane; import android.util.Log; import android.util.Size; +import com.android.internal.camera.flags.Flags; + import libcore.io.Memory; import java.nio.ByteBuffer; @@ -44,6 +46,11 @@ class ImageUtils { * are used. */ public static int getNumPlanesForFormat(int format) { + if (Flags.cameraHeifGainmap()) { + if (format == ImageFormat.HEIC_ULTRAHDR) { + return 1; + } + } switch (format) { case ImageFormat.YV12: case ImageFormat.YUV_420_888: @@ -229,6 +236,11 @@ class ImageUtils { public static int getEstimatedNativeAllocBytes(int width, int height, int format, int numImages) { double estimatedBytePerPixel; + if (Flags.cameraHeifGainmap()) { + if (format == ImageFormat.HEIC_ULTRAHDR) { + estimatedBytePerPixel = 0.3; + } + } switch (format) { // 10x compression from RGB_888 case ImageFormat.JPEG: @@ -283,6 +295,11 @@ class ImageUtils { } private static Size getEffectivePlaneSizeForImage(Image image, int planeIdx) { + if (Flags.cameraHeifGainmap()) { + if (image.getFormat() == ImageFormat.HEIC_ULTRAHDR){ + return new Size(image.getWidth(), image.getHeight()); + } + } switch (image.getFormat()) { case ImageFormat.YCBCR_P010: case ImageFormat.YV12: diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java index 5cbc81d5f92d..472d7985b304 100644 --- a/media/java/android/media/quality/MediaQualityContract.java +++ b/media/java/android/media/quality/MediaQualityContract.java @@ -21,11 +21,16 @@ import android.annotation.FlaggedApi; import android.media.tv.flags.Flags; /** + * The contract between the media quality service and applications. Contains definitions for the + * commonly used parameter names. * @hide */ @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) public class MediaQualityContract { + /** + * @hide + */ public interface BaseParameters { String PARAMETER_ID = "_id"; String PARAMETER_NAME = "_name"; @@ -33,13 +38,50 @@ public class MediaQualityContract { String PARAMETER_INPUT_ID = "_input_id"; } - public static final class PictureQuality implements BaseParameters { + + /** + * Parameters picture quality. + * @hide + */ + public static final class PictureQuality { + /** + * The brightness. + * + * <p>Type: INTEGER + */ public static final String PARAMETER_BRIGHTNESS = "brightness"; + + /** + * The contrast. + * + * <p>The ratio between the luminance of the brightest white and the darkest black. + * <p>Type: INTEGER + */ public static final String PARAMETER_CONTRAST = "contrast"; + + /** + * The sharpness. + * + * <p>Sharpness indicates the clarity of detail. + * <p>Type: INTEGER + */ public static final String PARAMETER_SHARPNESS = "sharpness"; + + /** + * The saturation. + * + * <p>Saturation indicates the intensity of the color. + * <p>Type: INTEGER + */ public static final String PARAMETER_SATURATION = "saturation"; + + private PictureQuality() { + } } + /** + * @hide + */ public static final class SoundQuality implements BaseParameters { public static final String PARAMETER_BALANCE = "balance"; public static final String PARAMETER_BASS = "bass"; diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index 1237d673162c..38a2025535f4 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -34,7 +34,8 @@ import java.util.List; import java.util.concurrent.Executor; /** - * Expose TV setting APIs for the application to use + * Central system API to the overall media quality, which arbitrates interaction between + * applications and media quality service. * @hide */ @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) @@ -177,6 +178,7 @@ public final class MediaQualityManager { * Gets picture profile by given profile ID. * @return the corresponding picture profile if available; {@code null} if the ID doesn't * exist or the profile is not accessible to the caller. + * @hide */ public PictureProfile getPictureProfileById(long profileId) { try { @@ -187,7 +189,10 @@ public final class MediaQualityManager { } - /** @SystemApi gets profiles that available to the given package */ + /** + * @SystemApi gets profiles that available to the given package + * @hide + */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public List<PictureProfile> getPictureProfilesByPackage(String packageName) { try { @@ -197,7 +202,10 @@ public final class MediaQualityManager { } } - /** Gets profiles that available to the caller package */ + /** + * Gets profiles that available to the caller. + */ + @NonNull public List<PictureProfile> getAvailablePictureProfiles() { try { return mService.getAvailablePictureProfiles(); @@ -206,7 +214,10 @@ public final class MediaQualityManager { } } - /** @SystemApi all stored picture profiles of all packages */ + /** + * @SystemApi all stored picture profiles of all packages + * @hide + */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public List<PictureProfile> getAllPictureProfiles() { try { @@ -221,6 +232,7 @@ public final class MediaQualityManager { * Creates a picture profile and store it in the system. * * @return the stored profile with an assigned profile ID. + * @hide */ public PictureProfile createPictureProfile(PictureProfile pp) { try { @@ -233,6 +245,7 @@ public final class MediaQualityManager { /** * Updates an existing picture profile and store it in the system. + * @hide */ public void updatePictureProfile(long profileId, PictureProfile pp) { try { @@ -245,6 +258,7 @@ public final class MediaQualityManager { /** * Removes a picture profile from the system. + * @hide */ public void removePictureProfile(long profileId) { try { @@ -291,6 +305,7 @@ public final class MediaQualityManager { * Gets sound profile by given profile ID. * @return the corresponding sound profile if available; {@code null} if the ID doesn't * exist or the profile is not accessible to the caller. + * @hide */ public SoundProfile getSoundProfileById(long profileId) { try { @@ -301,7 +316,10 @@ public final class MediaQualityManager { } - /** @SystemApi gets profiles that available to the given package */ + /** + * @SystemApi gets profiles that available to the given package + * @hide + */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public List<SoundProfile> getSoundProfilesByPackage(String packageName) { try { @@ -311,7 +329,10 @@ public final class MediaQualityManager { } } - /** Gets profiles that available to the caller package */ + /** + * Gets profiles that available to the caller package + * @hide + */ public List<SoundProfile> getAvailableSoundProfiles() { try { return mService.getAvailableSoundProfiles(); @@ -320,7 +341,10 @@ public final class MediaQualityManager { } } - /** @SystemApi all stored sound profiles of all packages */ + /** + * @SystemApi all stored sound profiles of all packages + * @hide + */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public List<SoundProfile> getAllSoundProfiles() { try { @@ -335,6 +359,7 @@ public final class MediaQualityManager { * Creates a sound profile and store it in the system. * * @return the stored profile with an assigned profile ID. + * @hide */ public SoundProfile createSoundProfile(SoundProfile sp) { try { @@ -347,6 +372,7 @@ public final class MediaQualityManager { /** * Updates an existing sound profile and store it in the system. + * @hide */ public void updateSoundProfile(long profileId, SoundProfile sp) { try { @@ -359,6 +385,7 @@ public final class MediaQualityManager { /** * Removes a sound profile from the system. + * @hide */ public void removeSoundProfile(long profileId) { try { @@ -370,6 +397,7 @@ public final class MediaQualityManager { /** * Gets capability information of the given parameters. + * @hide */ public List<ParamCapability> getParamCapabilities(List<String> names) { try { @@ -396,6 +424,7 @@ public final class MediaQualityManager { * different use cases. * * @param enabled {@code true} to enable, {@code false} to disable. + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setAutoPictureQualityEnabled(boolean enabled) { @@ -408,6 +437,7 @@ public final class MediaQualityManager { /** * Returns {@code true} if auto picture quality is enabled; {@code false} otherwise. + * @hide */ public boolean isAutoPictureQualityEnabled() { try { @@ -422,6 +452,7 @@ public final class MediaQualityManager { * <p>Super resolution is a feature to improve resolution. * * @param enabled {@code true} to enable, {@code false} to disable. + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setSuperResolutionEnabled(boolean enabled) { @@ -434,6 +465,7 @@ public final class MediaQualityManager { /** * Returns {@code true} if super resolution is enabled; {@code false} otherwise. + * @hide */ public boolean isSuperResolutionEnabled() { try { @@ -449,6 +481,7 @@ public final class MediaQualityManager { * different use cases. * * @param enabled {@code true} to enable, {@code false} to disable. + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public void setAutoSoundQualityEnabled(boolean enabled) { @@ -461,6 +494,7 @@ public final class MediaQualityManager { /** * Returns {@code true} if auto sound quality is enabled; {@code false} otherwise. + * @hide */ public boolean isAutoSoundQualityEnabled() { try { @@ -507,6 +541,7 @@ public final class MediaQualityManager { * Set the ambient backlight settings. * * @param settings The settings to use for the backlight detector. + * @hide */ public void setAmbientBacklightSettings( @NonNull AmbientBacklightSettings settings) { @@ -522,6 +557,7 @@ public final class MediaQualityManager { * Enables or disables the ambient backlight detection. * * @param enabled {@code true} to enable, {@code false} to disable. + * @hide */ public void setAmbientBacklightEnabled(boolean enabled) { try { @@ -700,6 +736,7 @@ public final class MediaQualityManager { public abstract static class AmbientBacklightCallback { /** * Called when new ambient backlight event is emitted. + * @hide */ public void onAmbientBacklightEvent(AmbientBacklightEvent event) { } diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java index 36c49d80e13d..8fb57124d33e 100644 --- a/media/java/android/media/quality/PictureProfile.java +++ b/media/java/android/media/quality/PictureProfile.java @@ -17,6 +17,8 @@ package android.media.quality; import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.media.tv.TvInputInfo; import android.media.tv.flags.Flags; import android.os.Bundle; import android.os.Parcel; @@ -24,30 +26,55 @@ import android.os.Parcelable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresPermission; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** + * Profile for picture quality. * @hide */ - @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) -public class PictureProfile implements Parcelable { +public final class PictureProfile implements Parcelable { @Nullable - private Long mId; + private String mId; + private final int mType; @NonNull private final String mName; @Nullable private final String mInputId; - @Nullable + @NonNull private final String mPackageName; @NonNull private final Bundle mParams; - protected PictureProfile(Parcel in) { - if (in.readByte() == 0) { - mId = null; - } else { - mId = in.readLong(); - } + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, prefix = "TYPE_", value = { + TYPE_SYSTEM, + TYPE_APPLICATION}) + public @interface ProfileType {} + + /** + * System profile type. + * + * <p>A profile of system type is managed by the system, and readable to the package define in + * {@link #getPackageName()}. + */ + public static final int TYPE_SYSTEM = 1; + /** + * Application profile type. + * + * <p>A profile of application type is managed by the package define in + * {@link #getPackageName()}. + */ + public static final int TYPE_APPLICATION = 2; + + + private PictureProfile(@NonNull Parcel in) { + mId = in.readString(); + mType = in.readInt(); mName = in.readString(); mInputId = in.readString(); mPackageName = in.readString(); @@ -55,13 +82,9 @@ public class PictureProfile implements Parcelable { } @Override - public void writeToParcel(Parcel dest, int flags) { - if (mId == null) { - dest.writeByte((byte) 0); - } else { - dest.writeByte((byte) 1); - dest.writeLong(mId); - } + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mId); + dest.writeInt(mType); dest.writeString(mName); dest.writeString(mInputId); dest.writeString(mPackageName); @@ -73,6 +96,7 @@ public class PictureProfile implements Parcelable { return 0; } + @NonNull public static final Creator<PictureProfile> CREATOR = new Creator<PictureProfile>() { @Override public PictureProfile createFromParcel(Parcel in) { @@ -92,95 +116,166 @@ public class PictureProfile implements Parcelable { * @hide */ public PictureProfile( - @Nullable Long id, + @Nullable String id, + int type, @NonNull String name, @Nullable String inputId, - @Nullable String packageName, + @NonNull String packageName, @NonNull Bundle params) { this.mId = id; + this.mType = type; this.mName = name; - com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name); this.mInputId = inputId; this.mPackageName = packageName; this.mParams = params; } + /** + * Gets profile ID. + * + * <p>A profile ID is a globally unique ID generated and assigned by the system. For profile + * objects retrieved from system (e.g {@link MediaQualityManager#getAvailablePictureProfiles()}) + * this profile ID is non-null; For profiles built locally with {@link Builder}, it's + * {@code null}. + * + * @return the unique profile ID; {@code null} if the profile is built locally with + * {@link Builder}. + */ @Nullable - public Long getProfileId() { + public String getProfileId() { return mId; } + /** + * Only used by system to assign the ID. + * @hide + */ + public void setProfileId(String id) { + mId = id; + } + + /** + * Gets profile type. + */ + @ProfileType + public int getProfileType() { + return mType; + } + + /** + * Gets the profile name. + */ @NonNull public String getName() { return mName; } + /** + * Gets the input ID if the profile is for a TV input. + * + * @return the corresponding TV input ID; {@code null} if the profile is not associated with a + * TV input. + * + * @see TvInputInfo#getId() + */ @Nullable public String getInputId() { return mInputId; } + /** + * Gets the package name of this profile. + * + * <p>The package name defines the user of a profile. Only this specific package and system app + * can access to this profile. + * + * @return the package name; {@code null} if the profile is built locally using + * {@link Builder} and the package is not set. + */ @Nullable public String getPackageName() { return mPackageName; } + + /** + * Gets the parameters of this profile. + * + * <p>The keys of commonly used parameters can be found in + * {@link MediaQualityContract.PictureQuality}. + */ @NonNull public Bundle getParameters() { return new Bundle(mParams); } /** - * A builder for {@link PictureProfile} + * A builder for {@link PictureProfile}. + * @hide */ - public static class Builder { + public static final class Builder { @Nullable - private Long mId; + private String mId; + private int mType = TYPE_APPLICATION; @NonNull private String mName; @Nullable private String mInputId; - @Nullable + @NonNull private String mPackageName; @NonNull private Bundle mParams; /** * Creates a new Builder. - * - * @hide */ public Builder(@NonNull String name) { mName = name; - com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name); } /** - * Copy constructor. - * - * @hide + * Copy constructor of builder. */ public Builder(@NonNull PictureProfile p) { mId = null; // ID needs to be reset + mType = p.getProfileType(); mName = p.getName(); mPackageName = p.getPackageName(); mInputId = p.getInputId(); + mParams = p.getParameters(); } /* @hide using by MediaQualityService */ /** - * Sets profile ID. - * @hide using by MediaQualityService + * Only used by system to assign the ID. + * @hide */ @NonNull - public Builder setProfileId(@Nullable Long id) { + public Builder setProfileId(@Nullable String id) { mId = id; return this; } /** + * Sets profile type. + * + * @hide @SystemApi + */ + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) + @NonNull + public Builder setProfileType(@ProfileType int value) { + mType = value; + return this; + } + + /** * Sets input ID. + * + * @see PictureProfile#getInputId() + * + * @hide @SystemApi */ + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) @NonNull public Builder setInputId(@NonNull String value) { mInputId = value; @@ -189,7 +284,12 @@ public class PictureProfile implements Parcelable { /** * Sets package name of the profile. + * + * @see PictureProfile#getPackageName() + * + * @hide @SystemApi */ + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) @NonNull public Builder setPackageName(@NonNull String value) { mPackageName = value; @@ -198,6 +298,8 @@ public class PictureProfile implements Parcelable { /** * Sets profile parameters. + * + * @see PictureProfile#getParameters() */ @NonNull public Builder setParameters(@NonNull Bundle params) { @@ -213,6 +315,7 @@ public class PictureProfile implements Parcelable { PictureProfile o = new PictureProfile( mId, + mType, mName, mInputId, mPackageName, diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java index 0e98488c93c0..c514f6ed04ef 100644 --- a/media/java/android/media/tv/TvInputServiceExtensionManager.java +++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java @@ -17,13 +17,17 @@ package android.media.tv; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringDef; import android.media.tv.flags.Flags; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -33,10 +37,14 @@ import java.util.Set; /** + * This class provides a list of available standardized TvInputService extension interface names + * and a container storing IBinder objects that implement these interfaces created by SoC/OEMs. + * It also provides an API for SoC/OEMs to register implemented IBinder objects. + * * @hide */ @FlaggedApi(Flags.FLAG_TIF_EXTENSION_STANDARDIZATION) -public class TvInputServiceExtensionManager { +public final class TvInputServiceExtensionManager { private static final String TAG = "TvInputServiceExtensionManager"; private static final String SCAN_PACKAGE = "android.media.tv.extension.scan."; private static final String OAD_PACKAGE = "android.media.tv.extension.oad."; @@ -54,404 +62,608 @@ public class TvInputServiceExtensionManager { private static final String ANALOG_PACKAGE = "android.media.tv.extension.analog."; private static final String TUNE_PACKAGE = "android.media.tv.extension.tune."; - /** Register binder returns success when it abides standardized interface structure */ + /** @hide */ + @IntDef(prefix = {"REGISTER_"}, value = { + REGISTER_SUCCESS, + REGISTER_FAIL_NAME_NOT_STANDARDIZED, + REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED, + REGISTER_FAIL_REMOTE_EXCEPTION + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RegisterResult {} + + /** + * Registering binder returns success when it abides standardized interface structure + */ public static final int REGISTER_SUCCESS = 0; - /** Register binder returns fail when the extension name is not in the standardization list */ + /** + * Registering binder returns failure when the extension name is not in the standardization + * list + */ public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1; - /** Register binder returns fail when the IBinder does not implement standardized interface */ + /** + * Registering binder returns failure when the IBinder does not implement standardized interface + */ public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2; - /** Register binder returns fail when remote server not available */ + /** + * Registering binder returns failure when remote server is not available + */ public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3; + /** @hide */ + @StringDef({ + ISCAN_INTERFACE, + ISCAN_SESSION, + ISCAN_LISTENER, + IHDPLUS_INFO, + IOPERATOR_DETECTION, + IOPERATOR_DETECTION_LISTENER, + IREGION_CHANNEL_LIST, + IREGION_CHANNEL_LIST_LISTENER, + ITARGET_REGION, + ITARGET_REGION_LISTENER, + ILCN_CONFLICT, + ILCN_CONFLICT_LISTENER, + ILCNV2_CHANNEL_LIST, + ILCNV2_CHANNEL_LIST_LISTENER, + IFAVORITE_NETWORK, + IFAVORITE_NETWORK_LISTENER, + ITKGS_INFO, + ITKGS_INFO_LISTENER, + ISCAN_SAT_SEARCH, + IOAD_UPDATE_INTERFACE, + ICAM_APP_INFO_SERVICE, + ICAM_APP_INFO_LISTENER, + ICAM_MONITORING_SERVICE, + ICAM_INFO_LISTENER, + ICI_OPERATOR_INTERFACE, + ICI_OPERATOR_LISTENER, + ICAM_PROFILE_INTERFACE, + ICONTENT_CONTROL_SERVICE, + ICAM_DRM_INFO_LISTENER, + ICAM_PIN_SERVICE, + ICAM_PIN_CAPABILITY_LISTENER, + ICAM_PIN_STATUS_LISTENER, + ICAM_HOST_CONTROL_SERVICE, + ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK, + ICAM_HOST_CONTROL_INFO_LISTENER, + ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG, + ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER, + IMMI_INTERFACE, + IMMI_SESSION, + IMMI_STATUS_CALLBACK, + IENTER_MENU_ERROR_CALLBACK, + IDOWNLOADABLE_RATING_TABLE_MONITOR, + IRATING_INTERFACE, + IPMT_RATING_INTERFACE, + IPMT_RATING_LISTENER, + IVBI_RATING_INTERFACE, + IVBI_RATING_LISTENER, + IPROGRAM_INFO, + IPROGRAM_INFO_LISTENER, + IBROADCAST_TIME, + IDATA_SERVICE_SIGNAL_INFO, + IDATA_SERVICE_SIGNAL_INFO_LISTENER, + ITELETEXT_PAGE_SUB_CODE, + ISCAN_BACKGROUND_SERVICE_UPDATE, + ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER, + ICLIENT_TOKEN, + ISCREEN_MODE_SETTINGS, + IHDMI_SIGNAL_INTERFACE, + IHDMI_SIGNAL_INFO_LISTENER, + IAUDIO_SIGNAL_INFO, + IANALOG_AUDIO_INFO, + IAUDIO_SIGNAL_INFO_LISTENER, + IVIDEO_SIGNAL_INFO, + IVIDEO_SIGNAL_INFO_LISTENER, + ISERVICE_LIST_EDIT, + ISERVICE_LIST_EDIT_LISTENER, + ISERVICE_LIST, + ISERVICE_LIST_TRANSFER_INTERFACE, + ISERVICE_LIST_EXPORT_SESSION, + ISERVICE_LIST_EXPORT_LISTENER, + ISERVICE_LIST_IMPORT_SESSION, + ISERVICE_LIST_IMPORT_LISTENER, + ISERVICE_LIST_SET_CHANNEL_LIST_SESSION, + ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER, + ICHANNEL_LIST_TRANSFER, + IRECORDED_CONTENTS, + IDELETE_RECORDED_CONTENTS_CALLBACK, + IGET_INFO_RECORDED_CONTENTS_CALLBACK, + IEVENT_MONITOR, + IEVENT_MONITOR_LISTENER, + IEVENT_DOWNLOAD, + IEVENT_DOWNLOAD_LISTENER, + IEVENT_DOWNLOAD_SESSION, + IANALOG_ATTRIBUTE_INTERFACE, + ICHANNEL_TUNED_INTERFACE, + ICHANNEL_TUNED_LISTENER, + ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE, + ITUNER_FRONTEND_SIGNAL_INFO_LISTENER, + IMUX_TUNE_SESSION, + IMUX_TUNE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StandardizedExtensionName {} /** * Interface responsible for creating scan session and obtain parameters. + * @hide */ public static final String ISCAN_INTERFACE = SCAN_PACKAGE + "IScanInterface"; /** * Interface that handles scan session and get/store related information. + * @hide */ public static final String ISCAN_SESSION = SCAN_PACKAGE + "IScanSession"; /** * Interface that notifies changes related to scan session. + * @hide */ public static final String ISCAN_LISTENER = SCAN_PACKAGE + "IScanListener"; /** * Interface for setting HDPlus information. + * @hide */ public static final String IHDPLUS_INFO = SCAN_PACKAGE + "IHDPlusInfo"; /** * Interface for handling operator detection for scanning. + * @hide */ public static final String IOPERATOR_DETECTION = SCAN_PACKAGE + "IOperatorDetection"; /** * Interface for changes related to operator detection searches. + * @hide */ public static final String IOPERATOR_DETECTION_LISTENER = SCAN_PACKAGE + "IOperatorDetectionListener"; /** * Interface for handling region channel list for scanning. + * @hide */ public static final String IREGION_CHANNEL_LIST = SCAN_PACKAGE + "IRegionChannelList"; /** * Interface for changes related to changes in region channel list search. + * @hide */ public static final String IREGION_CHANNEL_LIST_LISTENER = SCAN_PACKAGE + "IRegionChannelListListener"; /** * Interface for handling target region information. + * @hide */ public static final String ITARGET_REGION = SCAN_PACKAGE + "ITargetRegion"; /** * Interface for changes related to target regions during scanning. + * @hide */ public static final String ITARGET_REGION_LISTENER = SCAN_PACKAGE + "ITargetRegionListener"; /** * Interface for handling LCN conflict groups. + * @hide */ public static final String ILCN_CONFLICT = SCAN_PACKAGE + "ILcnConflict"; /** * Interface for detecting LCN conflicts during scanning. + * @hide */ public static final String ILCN_CONFLICT_LISTENER = SCAN_PACKAGE + "ILcnConflictListener"; /** * Interface for handling LCN V2 channel list information. + * @hide */ public static final String ILCNV2_CHANNEL_LIST = SCAN_PACKAGE + "ILcnV2ChannelList"; /** * Interface for detecting LCN V2 channel list during scanning. + * @hide */ public static final String ILCNV2_CHANNEL_LIST_LISTENER = SCAN_PACKAGE + "ILcnV2ChannelListListener"; /** * Interface for handling favorite network related information. + * @hide */ public static final String IFAVORITE_NETWORK = SCAN_PACKAGE + "IFavoriteNetwork"; /** * Interface for detecting favorite network during scanning. + * @hide */ public static final String IFAVORITE_NETWORK_LISTENER = SCAN_PACKAGE + "IFavoriteNetworkListener"; /** * Interface for handling Turksat channel update system service. + * @hide */ public static final String ITKGS_INFO = SCAN_PACKAGE + "ITkgsInfo"; /** * Interface for changes related to TKGS information. + * @hide */ public static final String ITKGS_INFO_LISTENER = SCAN_PACKAGE + "ITkgsInfoListener"; /** * Interface for satellite search related to low noise block downconverter. + * @hide */ public static final String ISCAN_SAT_SEARCH = SCAN_PACKAGE + "IScanSatSearch"; /** * Interface for Over-the-Air Download. + * @hide */ public static final String IOAD_UPDATE_INTERFACE = OAD_PACKAGE + "IOadUpdateInterface"; /** * Interface for handling conditional access module app related information. + * @hide */ public static final String ICAM_APP_INFO_SERVICE = CAM_PACKAGE + "ICamAppInfoService"; /** * Interface for changes on conditional access module app related information. + * @hide */ public static final String ICAM_APP_INFO_LISTENER = CAM_PACKAGE + "ICamAppInfoListener"; /** * Interface for handling conditional access module related information. + * @hide */ public static final String ICAM_MONITORING_SERVICE = CAM_PACKAGE + "ICamMonitoringService"; /** * Interface for changes on conditional access module related information. + * @hide */ public static final String ICAM_INFO_LISTENER = CAM_PACKAGE + "ICamInfoListener"; /** * Interface for handling control of CI+ operations. + * @hide */ public static final String ICI_OPERATOR_INTERFACE = CAM_PACKAGE + "ICiOperatorInterface"; /** * Interfaces for changes on CI+ operations. + * @hide */ public static final String ICI_OPERATOR_LISTENER = CAM_PACKAGE + "ICiOperatorListener"; /** * Interface for handling conditional access module profile related information. + * @hide */ public static final String ICAM_PROFILE_INTERFACE = CAM_PACKAGE + "ICamProfileInterface"; /** * Interface for handling conditional access module DRM related information. + * @hide */ public static final String ICONTENT_CONTROL_SERVICE = CAM_PACKAGE + "IContentControlService"; /** * Interface for changes on DRM. + * @hide */ public static final String ICAM_DRM_INFO_LISTENER = CAM_PACKAGE + "ICamDrmInfoListener"; /** * Interface for handling conditional access module pin related information. + * @hide */ public static final String ICAM_PIN_SERVICE = CAM_PACKAGE + "ICamPinService"; /** * Interface for changes on conditional access module pin capability. + * @hide */ public static final String ICAM_PIN_CAPABILITY_LISTENER = CAM_PACKAGE + "ICamPinCapabilityListener"; /** * Interface for changes on conditional access module pin status. + * @hide */ public static final String ICAM_PIN_STATUS_LISTENER = CAM_PACKAGE + "ICamPinStatusListener"; /** * Interface for handling conditional access module host control service. + * @hide */ public static final String ICAM_HOST_CONTROL_SERVICE = CAM_PACKAGE + "ICamHostControlService"; /** * Interface for handling conditional access module ask release reply. + * @hide */ public static final String ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK = CAM_PACKAGE + "ICamHostControlAskReleaseReplyCallback"; /** * Interface for changes on conditional access module host control service. + * @hide */ public static final String ICAM_HOST_CONTROL_INFO_LISTENER = CAM_PACKAGE + "ICamHostControlInfoListener"; /** * Interface for handling conditional access module host control service tune_quietly_flag. + * @hide */ public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG = CAM_PACKAGE + "ICamHostControlTuneQuietlyFlag"; /** * Interface for changes on conditional access module host control service tune_quietly_flag. + * @hide */ public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER = CAM_PACKAGE + "ICamHostControlTuneQuietlyFlagListener"; /** * Interface for handling conditional access module multi media interface. + * @hide */ public static final String IMMI_INTERFACE = CAM_PACKAGE + "IMmiInterface"; /** * Interface for controlling conditional access module multi media session. + * @hide */ public static final String IMMI_SESSION = CAM_PACKAGE + "IMmiSession"; /** * Interface for changes on conditional access module multi media session status. + * @hide */ public static final String IMMI_STATUS_CALLBACK = CAM_PACKAGE + "IMmiStatusCallback"; /** * Interface for changes on conditional access app info related to entering menu. + * @hide */ public static final String IENTER_MENU_ERROR_CALLBACK = CAM_PACKAGE + "IEnterMenuErrorCallback"; /** * Interface for handling RRT downloadable rating data. + * @hide */ public static final String IDOWNLOADABLE_RATING_TABLE_MONITOR = RATING_PACKAGE + "IDownloadableRatingTableMonitor"; /** * Interface for handling RRT rating related information. + * @hide */ public static final String IRATING_INTERFACE = RATING_PACKAGE + "IRatingInterface"; /** * Interface for handling PMT rating related information. + * @hide */ public static final String IPMT_RATING_INTERFACE = RATING_PACKAGE + "IPmtRatingInterface"; /** * Interface for changes on PMT rating related information. + * @hide */ public static final String IPMT_RATING_LISTENER = RATING_PACKAGE + "IPmtRatingListener"; /** * Interface for handling IVBI rating related information. + * @hide */ public static final String IVBI_RATING_INTERFACE = RATING_PACKAGE + "IVbiRatingInterface"; /** * Interface for changes on IVBI rating related information. + * @hide */ public static final String IVBI_RATING_LISTENER = RATING_PACKAGE + "IVbiRatingListener"; /** * Interface for handling program rating related information. + * @hide */ public static final String IPROGRAM_INFO = RATING_PACKAGE + "IProgramInfo"; /** * Interface for changes on program rating related information. + * @hide */ public static final String IPROGRAM_INFO_LISTENER = RATING_PACKAGE + "IProgramInfoListener"; /** * Interface for getting broadcast time related information. + * @hide */ - public static final String BROADCAST_TIME = TIME_PACKAGE + "BroadcastTime"; + public static final String IBROADCAST_TIME = TIME_PACKAGE + "BroadcastTime"; /** * Interface for handling data service signal information on teletext. + * @hide */ public static final String IDATA_SERVICE_SIGNAL_INFO = TELETEXT_PACKAGE + "IDataServiceSignalInfo"; /** * Interface for changes on data service signal information on teletext. + * @hide */ public static final String IDATA_SERVICE_SIGNAL_INFO_LISTENER = TELETEXT_PACKAGE + "IDataServiceSignalInfoListener"; /** * Interface for handling teletext page information. + * @hide */ public static final String ITELETEXT_PAGE_SUB_CODE = TELETEXT_PACKAGE + "ITeletextPageSubCode"; /** * Interface for handling scan background service update. + * @hide */ public static final String ISCAN_BACKGROUND_SERVICE_UPDATE = SCAN_BSU_PACKAGE + "IScanBackgroundServiceUpdate"; /** * Interface for changes on background service update + * @hide */ public static final String ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER = SCAN_BSU_PACKAGE + "IScanBackgroundServiceUpdateListener"; /** * Interface for generating client token. + * @hide */ public static final String ICLIENT_TOKEN = CLIENT_TOKEN_PACKAGE + "IClientToken"; /** * Interfaces for handling screen mode information. + * @hide */ public static final String ISCREEN_MODE_SETTINGS = SCREEN_MODE_PACKAGE + "IScreenModeSettings"; /** * Interfaces for handling HDMI signal information update. + * @hide */ public static final String IHDMI_SIGNAL_INTERFACE = SIGNAL_PACKAGE + "IHdmiSignalInterface"; /** * Interfaces for changes on HDMI signal information update. + * @hide */ public static final String IHDMI_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + "IHdmiSignalInfoListener"; /** * Interfaces for handling audio signal information update. + * @hide */ public static final String IAUDIO_SIGNAL_INFO = SIGNAL_PACKAGE + "IAudioSignalInfo"; /** * Interfaces for handling analog audio signal information update. + * @hide */ public static final String IANALOG_AUDIO_INFO = SIGNAL_PACKAGE + "IAnalogAudioInfo"; /** * Interfaces for change on audio signal information update. + * @hide */ public static final String IAUDIO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + "IAudioSignalInfoListener"; /** * Interfaces for handling video signal information update. + * @hide */ public static final String IVIDEO_SIGNAL_INFO = SIGNAL_PACKAGE + "IVideoSignalInfo"; /** * Interfaces for changes on video signal information update. + * @hide */ public static final String IVIDEO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + "IVideoSignalInfoListener"; /** * Interfaces for handling service database updates. + * @hide */ public static final String ISERVICE_LIST_EDIT = SERVICE_DATABASE_PACKAGE + "IServiceListEdit"; /** * Interfaces for changes on service database updates. + * @hide */ public static final String ISERVICE_LIST_EDIT_LISTENER = SERVICE_DATABASE_PACKAGE + "IServiceListEditListener"; /** * Interfaces for getting service database related information. + * @hide */ public static final String ISERVICE_LIST = SERVICE_DATABASE_PACKAGE + "IServiceList"; /** * Interfaces for transferring service database related information. + * @hide */ public static final String ISERVICE_LIST_TRANSFER_INTERFACE = SERVICE_DATABASE_PACKAGE + "IServiceListTransferInterface"; /** * Interfaces for exporting service database session. + * @hide */ public static final String ISERVICE_LIST_EXPORT_SESSION = SERVICE_DATABASE_PACKAGE + "IServiceListExportSession"; /** * Interfaces for changes on exporting service database session. + * @hide */ public static final String ISERVICE_LIST_EXPORT_LISTENER = SERVICE_DATABASE_PACKAGE + "IServiceListExportListener"; /** * Interfaces for importing service database session. + * @hide */ public static final String ISERVICE_LIST_IMPORT_SESSION = SERVICE_DATABASE_PACKAGE + "IServiceListImportSession"; /** * Interfaces for changes on importing service database session. + * @hide */ public static final String ISERVICE_LIST_IMPORT_LISTENER = SERVICE_DATABASE_PACKAGE + "IServiceListImportListener"; /** * Interfaces for setting channel list resources. + * @hide */ public static final String ISERVICE_LIST_SET_CHANNEL_LIST_SESSION = SERVICE_DATABASE_PACKAGE + "IServiceListSetChannelListSession"; /** * Interfaces for changes on setting channel list resources. + * @hide */ public static final String ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER = SERVICE_DATABASE_PACKAGE + "IServiceListSetChannelListListener"; /** * Interfaces for transferring channel list resources. + * @hide */ public static final String ICHANNEL_LIST_TRANSFER = SERVICE_DATABASE_PACKAGE + "IChannelListTransfer"; /** * Interfaces for record contents updates. + * @hide */ public static final String IRECORDED_CONTENTS = PVR_PACKAGE + "IRecordedContents"; /** * Interfaces for changes on deleting record contents. + * @hide */ public static final String IDELETE_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE + "IDeleteRecordedContentsCallback"; /** * Interfaces for changes on getting record contents. + * @hide */ public static final String IGET_INFO_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE + "IGetInfoRecordedContentsCallback"; /** * Interfaces for monitoring present event information. + * @hide */ public static final String IEVENT_MONITOR = EVENT_PACKAGE + "IEventMonitor"; /** * Interfaces for changes on present event information. + * @hide */ public static final String IEVENT_MONITOR_LISTENER = EVENT_PACKAGE + "IEventMonitorListener"; /** * Interfaces for handling download event information. + * @hide */ public static final String IEVENT_DOWNLOAD = EVENT_PACKAGE + "IEventDownload"; /** * Interfaces for changes on downloading event information. + * @hide */ public static final String IEVENT_DOWNLOAD_LISTENER = EVENT_PACKAGE + "IEventDownloadListener"; /** * Interfaces for handling download event information for DVB and DTMB. + * @hide */ public static final String IEVENT_DOWNLOAD_SESSION = EVENT_PACKAGE + "IEventDownloadSession"; /** * Interfaces for handling analog color system. + * @hide */ public static final String IANALOG_ATTRIBUTE_INTERFACE = ANALOG_PACKAGE + "IAnalogAttributeInterface"; /** * Interfaces for monitoring channel tuned information. + * @hide */ public static final String ICHANNEL_TUNED_INTERFACE = TUNE_PACKAGE + "IChannelTunedInterface"; /** * Interfaces for changes on channel tuned information. + * @hide */ public static final String ICHANNEL_TUNED_LISTENER = TUNE_PACKAGE + "IChannelTunedListener"; /** * Interfaces for handling tuner frontend signal info. + * @hide */ public static final String ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE = SIGNAL_PACKAGE + "ITunerFrontendSignalInfoInterface"; /** * Interfaces for changes on tuner frontend signal info. + * @hide */ public static final String ITUNER_FRONTEND_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + "ITunerFrontendSignalInfoListener"; /** * Interfaces for handling mux tune operations. + * @hide */ public static final String IMUX_TUNE_SESSION = TUNE_PACKAGE + "IMuxTuneSession"; /** * Interfaces for initing mux tune session. + * @hide */ public static final String IMUX_TUNE = TUNE_PACKAGE + "IMuxTune"; @@ -506,7 +718,7 @@ public class TvInputServiceExtensionManager { IVBI_RATING_LISTENER, IPROGRAM_INFO, IPROGRAM_INFO_LISTENER, - BROADCAST_TIME, + IBROADCAST_TIME, IDATA_SERVICE_SIGNAL_INFO, IDATA_SERVICE_SIGNAL_INFO_LISTENER, ITELETEXT_PAGE_SUB_CODE, @@ -586,7 +798,8 @@ public class TvInputServiceExtensionManager { * * @hide */ - public int registerExtensionIBinder(@NonNull String extensionName, + @RegisterResult + public int registerExtensionIBinder(@StandardizedExtensionName @NonNull String extensionName, @NonNull IBinder binder) { if (!checkIsStandardizedInterfaces(extensionName)) { return REGISTER_FAIL_NAME_NOT_STANDARDIZED; diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp index 0fb3049f63d8..23dd9b7aee37 100644 --- a/native/graphics/jni/Android.bp +++ b/native/graphics/jni/Android.bp @@ -48,7 +48,9 @@ cc_library_shared { "libhwui_internal_headers", ], - static_libs: ["libarect"], + static_libs: [ + "libarect", + ], host_supported: true, target: { @@ -60,6 +62,11 @@ cc_library_shared { shared_libs: [ "libandroid", ], + static_libs: [ + "libstatslog_hwui", + "libstatspull_lazy", + "libstatssocket_lazy", + ], version_script: "libjnigraphics.map.txt", }, host: { diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp index e18b4a9d2420..cb95bcf5d2b1 100644 --- a/native/graphics/jni/imagedecoder.cpp +++ b/native/graphics/jni/imagedecoder.cpp @@ -14,18 +14,9 @@ * limitations under the License. */ -#include "aassetstreamadaptor.h" - -#include <android/asset_manager.h> -#include <android/bitmap.h> -#include <android/data_space.h> -#include <android/imagedecoder.h> #include <MimeType.h> -#include <android/rect.h> -#include <hwui/ImageDecoder.h> -#include <log/log.h> -#include <SkAndroidCodec.h> #include <SkAlphaType.h> +#include <SkAndroidCodec.h> #include <SkCodec.h> #include <SkCodecAnimation.h> #include <SkColorSpace.h> @@ -35,14 +26,24 @@ #include <SkRefCnt.h> #include <SkSize.h> #include <SkStream.h> -#include <utils/Color.h> - +#include <android/asset_manager.h> +#include <android/bitmap.h> +#include <android/data_space.h> +#include <android/imagedecoder.h> +#include <android/rect.h> #include <fcntl.h> -#include <limits> -#include <optional> +#include <hwui/ImageDecoder.h> +#include <log/log.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> +#include <utils/Color.h> +#include <utils/StatsUtils.h> + +#include <limits> +#include <optional> + +#include "aassetstreamadaptor.h" using namespace android; @@ -400,9 +401,7 @@ size_t AImageDecoder_getMinimumStride(AImageDecoder* decoder) { return info.minRowBytes(); } -int AImageDecoder_decodeImage(AImageDecoder* decoder, - void* pixels, size_t stride, - size_t size) { +int AImageDecoder_decodeImage(AImageDecoder* decoder, void* pixels, size_t stride, size_t size) { if (!decoder || !pixels || !stride) { return ANDROID_IMAGE_DECODER_BAD_PARAMETER; } @@ -419,7 +418,13 @@ int AImageDecoder_decodeImage(AImageDecoder* decoder, return ANDROID_IMAGE_DECODER_FINISHED; } - return ResultToErrorCode(imageDecoder->decode(pixels, stride)); + const auto result = ResultToErrorCode(imageDecoder->decode(pixels, stride)); + + if (result == ANDROID_IMAGE_DECODER_SUCCESS) { + uirenderer::logBitmapDecode(imageDecoder->getOutputInfo(), false); + } + + return result; } void AImageDecoder_delete(AImageDecoder* decoder) { diff --git a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java index f1103e19c90e..992f581a8a70 100644 --- a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java +++ b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java @@ -896,7 +896,8 @@ public class RescueParty { int systemUserId = UserHandle.SYSTEM.getIdentifier(); int[] userIds = { systemUserId }; try { - for (File file : FileUtils.listFilesOrEmpty(Environment.getDataSystemDeDirectory())) { + for (File file : FileUtils.listFilesOrEmpty( + Environment.getDataSystemDeviceProtectedDirectory())) { try { final int userId = Integer.parseInt(file.getName()); if (userId != systemUserId) { diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 8277e573e7c2..311def80f248 100644 --- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -499,8 +499,8 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve // Check if the package is listed among the system modules or is an // APK inside an updatable APEX. try { - final PackageInfo pkg = mContext.getPackageManager() - .getPackageInfo(packageName, 0 /* flags */); + PackageManager pm = mContext.getPackageManager(); + final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */); String apexPackageName = pkg.getApexPackageName(); if (apexPackageName != null) { packageName = apexPackageName; diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java index 2acedd56e505..be339cdb75ab 100644 --- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java @@ -300,6 +300,31 @@ public class PackageWatchdog { sPackageWatchdog = this; } + /** + * Creating this temp constructor to match module constructor. + * Note: To be only used in tests. + * Creates a PackageWatchdog that allows injecting dependencies, + * except for connectivity module connector. + */ + @VisibleForTesting + PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler, + Handler longTaskHandler, ExplicitHealthCheckController controller, + SystemClock clock) { + mContext = context; + mPolicyFile = policyFile; + mShortTaskHandler = shortTaskHandler; + mLongTaskHandler = longTaskHandler; + mHealthCheckController = controller; + mConnectivityModuleConnector = ConnectivityModuleConnector.getInstance(); + mSystemClock = clock; + mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS; + mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS); + + loadFromFile(); + sPackageWatchdog = this; + } + /** Creates or gets singleton instance of PackageWatchdog. */ public static @NonNull PackageWatchdog getInstance(@NonNull Context context) { synchronized (sPackageWatchdogLock) { diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt index 72cf40387a25..49acc1d44144 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt @@ -115,7 +115,7 @@ interface PreferenceScreenCreator : PreferenceScreenMetadata, PreferenceScreenPr fun isFlagEnabled(context: Context): Boolean = true val preferenceBindingFactory: PreferenceBindingFactory - get() = DefaultPreferenceBindingFactory + get() = PreferenceBindingFactory.defaultFactory override fun createPreferenceScreen(factory: PreferenceScreenFactory) = factory.getOrCreatePreferenceScreen().apply { diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt index 87c289f48ff0..51b9aac029a5 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt @@ -45,10 +45,15 @@ interface PreferenceBindingFactory { /** Returns the [PreferenceBinding] associated with the [PreferenceMetadata]. */ fun getPreferenceBinding(metadata: PreferenceMetadata): PreferenceBinding? + + companion object { + /** Default preference binding factory. */ + @JvmStatic var defaultFactory: PreferenceBindingFactory = DefaultPreferenceBindingFactory() + } } /** Default [PreferenceBindingFactory]. */ -object DefaultPreferenceBindingFactory : PreferenceBindingFactory { +open class DefaultPreferenceBindingFactory : PreferenceBindingFactory { override fun getPreferenceBinding(metadata: PreferenceMetadata) = metadata as? PreferenceBinding @@ -66,5 +71,6 @@ class KeyedPreferenceBindingFactory(private val bindings: Map<String, Preference PreferenceBindingFactory { override fun getPreferenceBinding(metadata: PreferenceMetadata) = - bindings[metadata.key] ?: DefaultPreferenceBindingFactory.getPreferenceBinding(metadata) + bindings[metadata.key] + ?: PreferenceBindingFactory.defaultFactory.getPreferenceBinding(metadata) } diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt index f3142d031aa9..00bad5203f07 100644 --- a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt +++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt @@ -25,7 +25,7 @@ import com.android.settingslib.metadata.PreferenceMetadata /** Creates [Preference] widget and binds with metadata. */ @VisibleForTesting fun <P : Preference> PreferenceMetadata.createAndBindWidget(context: Context): P { - val binding = DefaultPreferenceBindingFactory.getPreferenceBinding(this) + val binding = PreferenceBindingFactory.defaultFactory.getPreferenceBinding(this)!! return (binding.createWidget(context) as P).also { if (this is PersistentPreference<*>) { storage(context)?.let { keyValueStore -> diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java index 6578eb7d50a6..c36ade979d47 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java @@ -22,14 +22,20 @@ import androidx.annotation.NonNull; import androidx.preference.DropDownPreference; import androidx.preference.PreferenceViewHolder; -public class RestrictedDropDownPreference extends DropDownPreference { - RestrictedPreferenceHelper mHelper; +public class RestrictedDropDownPreference extends DropDownPreference implements + RestrictedPreferenceHelperProvider { + private final RestrictedPreferenceHelper mHelper; public RestrictedDropDownPreference(@NonNull Context context) { super(context); mHelper = new RestrictedPreferenceHelper(context, this, null); } + @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + /** * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this * package. Marks the preference as disabled if so. diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedInterface.kt b/packages/SettingsLib/src/com/android/settingslib/RestrictedInterface.kt deleted file mode 100644 index 14f9a19ee753..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedInterface.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib - -import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin - -interface RestrictedInterface { - fun useAdminDisabledSummary(useSummary: Boolean) - - fun checkRestrictionAndSetDisabled(userRestriction: String) - - fun checkRestrictionAndSetDisabled(userRestriction: String, userId: Int) - - /** - * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this - * package. Marks the preference as disabled if so. - * - * @param settingIdentifier The key identifying the setting - * @param packageName the package to check the settingIdentifier for - */ - fun checkEcmRestrictionAndSetDisabled( - settingIdentifier: String, - packageName: String - ) - - val isDisabledByAdmin: Boolean - - fun setDisabledByAdmin(admin: EnforcedAdmin?) - - val isDisabledByEcm: Boolean - - val uid: Int - - val packageName: String? -} diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java index 495410b0a8ae..332042a5c4f9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java @@ -34,7 +34,9 @@ import com.android.settingslib.widget.TwoTargetPreference; * Preference class that supports being disabled by a user restriction * set by a device admin. */ -public class RestrictedPreference extends TwoTargetPreference { +public class RestrictedPreference extends TwoTargetPreference implements + RestrictedPreferenceHelperProvider { + RestrictedPreferenceHelper mHelper; public RestrictedPreference(Context context, AttributeSet attrs, @@ -67,6 +69,11 @@ public class RestrictedPreference extends TwoTargetPreference { } @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + + @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); mHelper.onBindViewHolder(holder); diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt new file mode 100644 index 000000000000..f2860845e3d3 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib + +/** Provider of [RestrictedPreferenceHelper]. */ +interface RestrictedPreferenceHelperProvider { + + /** Returns the [RestrictedPreferenceHelper] applied to the preference. */ + fun getRestrictedPreferenceHelper(): RestrictedPreferenceHelper +} diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java index c52c7ea7328b..573869db5073 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java @@ -32,8 +32,9 @@ import com.android.settingslib.widget.SelectorWithWidgetPreference; /** * Selector with widget preference that can be disabled by a device admin using a user restriction. */ -public class RestrictedSelectorWithWidgetPreference extends SelectorWithWidgetPreference { - private RestrictedPreferenceHelper mHelper; +public class RestrictedSelectorWithWidgetPreference extends SelectorWithWidgetPreference implements + RestrictedPreferenceHelperProvider { + private final RestrictedPreferenceHelper mHelper; /** * Perform inflation from XML and apply a class-specific base style. @@ -82,8 +83,11 @@ public class RestrictedSelectorWithWidgetPreference extends SelectorWithWidgetPr */ public RestrictedSelectorWithWidgetPreference(@NonNull Context context) { this(context, null); - mHelper = - new RestrictedPreferenceHelper(context, /* preference= */ this, /* attrs= */ null); + } + + @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java index 1dc5281c266c..b690816a6fb6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java @@ -19,7 +19,6 @@ package com.android.settingslib; import android.annotation.SuppressLint; import android.content.Context; import android.os.Process; -import android.os.UserHandle; import android.util.AttributeSet; import androidx.annotation.NonNull; @@ -34,8 +33,10 @@ import com.android.settingslib.widget.SliderPreference; * Slide Preference that supports being disabled by a user restriction * set by a device admin. */ -public class RestrictedSliderPreference extends SliderPreference implements RestrictedInterface { - RestrictedPreferenceHelper mHelper; +public class RestrictedSliderPreference extends SliderPreference implements + RestrictedPreferenceHelperProvider { + + private final RestrictedPreferenceHelper mHelper; public RestrictedSliderPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, @Nullable String packageName, int uid) { @@ -66,6 +67,11 @@ public class RestrictedSliderPreference extends SliderPreference implements Rest } @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + + @Override public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { super.onBindViewHolder(holder); mHelper.onBindViewHolder(holder); @@ -81,12 +87,12 @@ public class RestrictedSliderPreference extends SliderPreference implements Rest @Override public void setEnabled(boolean enabled) { - if (enabled && isDisabledByAdmin()) { + if (enabled && mHelper.isDisabledByAdmin()) { mHelper.setDisabledByAdmin(null); return; } - if (enabled && isDisabledByEcm()) { + if (enabled && mHelper.isDisabledByEcm()) { mHelper.setDisabledByEcm(null); return; } @@ -95,55 +101,6 @@ public class RestrictedSliderPreference extends SliderPreference implements Rest } @Override - public void useAdminDisabledSummary(boolean useSummary) { - mHelper.useAdminDisabledSummary(useSummary); - } - - @Override - public void checkRestrictionAndSetDisabled(@NonNull String userRestriction) { - mHelper.checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId()); - } - - @Override - public void checkRestrictionAndSetDisabled(@NonNull String userRestriction, int userId) { - mHelper.checkRestrictionAndSetDisabled(userRestriction, userId); - } - - @Override - public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, - @NonNull String packageName) { - mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName); - } - - @Override - public void setDisabledByAdmin(@Nullable RestrictedLockUtils.EnforcedAdmin admin) { - if (mHelper.setDisabledByAdmin(admin)) { - notifyChanged(); - } - } - - @Override - public boolean isDisabledByAdmin() { - return mHelper.isDisabledByAdmin(); - } - - @Override - public boolean isDisabledByEcm() { - return mHelper.isDisabledByEcm(); - } - - @Override - public int getUid() { - return mHelper != null ? mHelper.uid : Process.INVALID_UID; - } - - @Override - @Nullable - public String getPackageName() { - return mHelper != null ? mHelper.packageName : null; - } - - @Override protected void onAttachedToHierarchy(@NonNull PreferenceManager preferenceManager) { mHelper.onAttachedToHierarchy(); super.onAttachedToHierarchy(preferenceManager); diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index fffbb547c662..727dbe1019ae 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -45,8 +45,9 @@ import androidx.preference.SwitchPreferenceCompat; * Version of SwitchPreferenceCompat that can be disabled by a device admin * using a user restriction. */ -public class RestrictedSwitchPreference extends SwitchPreferenceCompat { - RestrictedPreferenceHelper mHelper; +public class RestrictedSwitchPreference extends SwitchPreferenceCompat implements + RestrictedPreferenceHelperProvider { + private final RestrictedPreferenceHelper mHelper; AppOpsManager mAppOpsManager; boolean mUseAdditionalSummary = false; CharSequence mRestrictedSwitchSummary; @@ -98,6 +99,11 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat { this(context, null); } + @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + @VisibleForTesting public void setAppOps(AppOpsManager appOps) { mAppOpsManager = appOps; diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java index 0096015aa875..34e4d8f7346f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java @@ -22,14 +22,16 @@ import android.content.Context; import android.os.UserHandle; import android.util.AttributeSet; +import androidx.annotation.NonNull; import androidx.core.content.res.TypedArrayUtils; import androidx.preference.Preference; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceViewHolder; /** Top level preference that can be disabled by a device admin using a user restriction. */ -public class RestrictedTopLevelPreference extends Preference { - private RestrictedPreferenceHelper mHelper; +public class RestrictedTopLevelPreference extends Preference implements + RestrictedPreferenceHelperProvider { + private final RestrictedPreferenceHelper mHelper; public RestrictedTopLevelPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { @@ -51,6 +53,11 @@ public class RestrictedTopLevelPreference extends Preference { } @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + + @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); mHelper.onBindViewHolder(holder); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 1919572ff571..2c8c261fa8f8 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -715,6 +715,9 @@ <uses-permission android:name="android.permission.UWB_PRIVILEGED" /> <uses-permission android:name="android.permission.UWB_RANGING" /> + <!-- Permission required for CTS test - CtsRangingTestCases --> + <uses-permission android:name="android.permission.RANGING" /> + <!-- Permission required for CTS test - CtsAlarmManagerTestCases --> <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index f0e1b437ec51..e0117368515b 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -1013,6 +1013,7 @@ android:autoRemoveFromRecents="true" android:launchMode="singleTop" android:showForAllUsers="true" + android:turnScreenOn="true" android:exported="false"> </activity> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 13a5248e193b..3bf3e24a2ba6 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -26,6 +26,16 @@ flag { } flag { + name: "user_encrypted_source" + namespace: "systemui" + description: "Get rid of the local cache and rely on UserManager.isUserUnlocked directly to determine whether user CE storage is encrypted." + bug: "333656491" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "modes_ui_dialog_paging" namespace: "systemui" description: "Add pagination to the Modes dialog in quick settings." @@ -1578,6 +1588,13 @@ flag { } flag { + name: "show_clipboard_indication" + namespace: "systemui" + description: "Show indication text under the clipboard overlay when copied something" + bug: "361199935" +} + +flag { name: "media_projection_dialog_behind_lockscreen" namespace: "systemui" description: "Ensure MediaProjection Dialog appears behind the lockscreen" diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt index 4cf264253bf8..fdb4871423c3 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt @@ -20,6 +20,7 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context +import android.graphics.PointF import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import android.graphics.drawable.GradientDrawable @@ -33,13 +34,13 @@ import android.view.ViewOverlay import android.view.animation.Interpolator import android.window.WindowAnimationState import com.android.app.animation.Interpolators.LINEAR -import com.android.app.animation.MathUtils.max import com.android.internal.annotations.VisibleForTesting import com.android.internal.dynamicanimation.animation.SpringAnimation import com.android.internal.dynamicanimation.animation.SpringForce import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary import java.util.concurrent.Executor import kotlin.math.abs +import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt @@ -91,6 +92,14 @@ class TransitionAnimator( ) } + /** + * Similar to [getProgress] above, bug the delay and duration are expressed as percentages + * of the animation duration (between 0f and 1f). + */ + internal fun getProgress(linearProgress: Float, delay: Float, duration: Float): Float { + return getProgressInternal(totalDuration = 1f, linearProgress, delay, duration) + } + private fun getProgressInternal( totalDuration: Float, linearProgress: Float, @@ -262,10 +271,10 @@ class TransitionAnimator( var centerY: Float, var scale: Float = 0f, - // Cached values. - var previousCenterX: Float = -1f, - var previousCenterY: Float = -1f, - var previousScale: Float = -1f, + // Update flags (used to decide whether it's time to update the transition state). + var isCenterXUpdated: Boolean = false, + var isCenterYUpdated: Boolean = false, + var isScaleUpdated: Boolean = false, // Completion flags. var isCenterXDone: Boolean = false, @@ -286,6 +295,7 @@ class TransitionAnimator( override fun setValue(state: SpringState, value: Float) { state.centerX = value + state.isCenterXUpdated = true } }, CENTER_Y { @@ -295,6 +305,7 @@ class TransitionAnimator( override fun setValue(state: SpringState, value: Float) { state.centerY = value + state.isCenterYUpdated = true } }, SCALE { @@ -304,6 +315,7 @@ class TransitionAnimator( override fun setValue(state: SpringState, value: Float) { state.scale = value + state.isScaleUpdated = true } }; @@ -444,8 +456,8 @@ class TransitionAnimator( * punching a hole in the [transition container][Controller.transitionContainer]) iff [drawHole] * is true. * - * If [useSpring] is true, a multi-spring animation will be used instead of the default - * interpolators. + * If [startVelocity] (expressed in pixels per second) is not null, a multi-spring animation + * using it for the initial momentum will be used instead of the default interpolators. */ fun startAnimation( controller: Controller, @@ -453,9 +465,9 @@ class TransitionAnimator( windowBackgroundColor: Int, fadeWindowBackgroundLayer: Boolean = true, drawHole: Boolean = false, - useSpring: Boolean = false, + startVelocity: PointF? = null, ): Animation { - if (!controller.isLaunching || useSpring) checkReturnAnimationFrameworkFlag() + if (!controller.isLaunching || startVelocity != null) checkReturnAnimationFrameworkFlag() // We add an extra layer with the same color as the dialog/app splash screen background // color, which is usually the same color of the app background. We first fade in this layer @@ -474,7 +486,7 @@ class TransitionAnimator( windowBackgroundLayer, fadeWindowBackgroundLayer, drawHole, - useSpring, + startVelocity, ) .apply { start() } } @@ -487,7 +499,7 @@ class TransitionAnimator( windowBackgroundLayer: GradientDrawable, fadeWindowBackgroundLayer: Boolean = true, drawHole: Boolean = false, - useSpring: Boolean = false, + startVelocity: PointF? = null, ): Animation { val transitionContainer = controller.transitionContainer val transitionContainerOverlay = transitionContainer.overlay @@ -504,11 +516,12 @@ class TransitionAnimator( openingWindowSyncView != null && openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl - return if (useSpring && springTimings != null && springInterpolators != null) { + return if (startVelocity != null && springTimings != null && springInterpolators != null) { createSpringAnimation( controller, startState, endState, + startVelocity, windowBackgroundLayer, transitionContainer, transitionContainerOverlay, @@ -693,6 +706,7 @@ class TransitionAnimator( controller: Controller, startState: State, endState: State, + startVelocity: PointF, windowBackgroundLayer: GradientDrawable, transitionContainer: View, transitionContainerOverlay: ViewGroupOverlay, @@ -721,19 +735,20 @@ class TransitionAnimator( fun updateProgress(state: SpringState) { if ( - (!state.isCenterXDone && state.centerX == state.previousCenterX) || - (!state.isCenterYDone && state.centerY == state.previousCenterY) || - (!state.isScaleDone && state.scale == state.previousScale) + !(state.isCenterXUpdated || state.isCenterXDone) || + !(state.isCenterYUpdated || state.isCenterYDone) || + !(state.isScaleUpdated || state.isScaleDone) ) { // Because all three springs use the same update method, we only actually update - // when all values have changed, avoiding two redundant calls per frame. + // when all properties have received their new value (which could be unchanged from + // the previous one), avoiding two redundant calls per frame. return } - // Update the latest values for the check above. - state.previousCenterX = state.centerX - state.previousCenterY = state.centerY - state.previousScale = state.scale + // Reset the update flags. + state.isCenterXUpdated = false + state.isCenterYUpdated = false + state.isScaleUpdated = false // Current scale-based values, that will be used to find the new animation bounds. val width = @@ -829,6 +844,7 @@ class TransitionAnimator( } setStartValue(startState.centerX) + setStartVelocity(startVelocity.x) setMinValue(min(startState.centerX, endState.centerX)) setMaxValue(max(startState.centerX, endState.centerX)) @@ -850,6 +866,7 @@ class TransitionAnimator( } setStartValue(startState.centerY) + setStartVelocity(startVelocity.y) setMinValue(min(startState.centerY, endState.centerY)) setMaxValue(max(startState.centerY, endState.centerY)) @@ -1057,15 +1074,13 @@ class TransitionAnimator( interpolators = springInterpolators!! val timings = springTimings!! fadeInProgress = - getProgressInternal( - totalDuration = 1f, + getProgress( linearProgress, timings.contentBeforeFadeOutDelay, timings.contentBeforeFadeOutDuration, ) fadeOutProgress = - getProgressInternal( - totalDuration = 1f, + getProgress( linearProgress, timings.contentAfterFadeInDelay, timings.contentAfterFadeInDuration, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index 63c5d7aed3e1..e7b66c5f0d2f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -52,6 +52,7 @@ import com.android.compose.animation.scene.content.Content import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.SharedElementTransformation +import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.drawInContainer import com.android.compose.ui.util.lerp @@ -187,6 +188,7 @@ private fun Modifier.maybeElevateInContent( state.transformationSpec .transformations(key, content.key) .shared + ?.transformation ?.elevateInContent == content.key && isSharedElement(stateByContent, state) && isSharedElementEnabled(key, state) && @@ -901,7 +903,7 @@ private fun shouldPlaceElement( } val sharedTransformation = sharedElementTransformation(element.key, transition) - if (sharedTransformation?.enabled == false) { + if (sharedTransformation?.transformation?.enabled == false) { return true } @@ -954,13 +956,13 @@ private fun isSharedElementEnabled( element: ElementKey, transition: TransitionState.Transition, ): Boolean { - return sharedElementTransformation(element, transition)?.enabled ?: true + return sharedElementTransformation(element, transition)?.transformation?.enabled ?: true } internal fun sharedElementTransformation( element: ElementKey, transition: TransitionState.Transition, -): SharedElementTransformation? { +): TransformationWithRange<SharedElementTransformation>? { val transformationSpec = transition.transformationSpec val sharedInFromContent = transformationSpec.transformations(element, transition.fromContent).shared @@ -1244,7 +1246,7 @@ private inline fun <T> computeValue( element: Element, transition: TransitionState.Transition?, contentValue: (Element.State) -> T, - transformation: (ElementTransformations) -> PropertyTransformation<T>?, + transformation: (ElementTransformations) -> TransformationWithRange<PropertyTransformation<T>>?, currentValue: () -> T, isSpecified: (T) -> Boolean, lerp: (T, T, Float) -> T, @@ -1280,7 +1282,7 @@ private inline fun <T> computeValue( checkNotNull(if (currentContent == toContent) toState else fromState) val idleValue = contentValue(overscrollState) val targetValue = - with(propertySpec) { + with(propertySpec.transformation) { layoutImpl.propertyTransformationScope.transform( currentContent, element.key, @@ -1375,7 +1377,7 @@ private inline fun <T> computeValue( val idleValue = contentValue(contentState) val isEntering = content == toContent val previewTargetValue = - with(previewTransformation) { + with(previewTransformation.transformation) { layoutImpl.propertyTransformationScope.transform( content, element.key, @@ -1386,7 +1388,7 @@ private inline fun <T> computeValue( val targetValueOrNull = transformation?.let { transformation -> - with(transformation) { + with(transformation.transformation) { layoutImpl.propertyTransformationScope.transform( content, element.key, @@ -1461,7 +1463,7 @@ private inline fun <T> computeValue( val idleValue = contentValue(contentState) val targetValue = - with(transformation) { + with(transformation.transformation) { layoutImpl.propertyTransformationScope.transform( content, element.key, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index e1e2411da080..61332b61ed1b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -764,7 +764,8 @@ internal class MutableSceneTransitionLayoutStateImpl( return@fastForEach } - state.transformationSpec.transformations.fastForEach { transformation -> + state.transformationSpec.transformations.fastForEach { transformationWithRange -> + val transformation = transformationWithRange.transformation if ( transformation is SharedElementTransformation && transformation.elevateInContent != null diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index 8866fbfbf194..b083f79aebf5 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -33,10 +33,10 @@ import com.android.compose.animation.scene.transformation.EdgeTranslate import com.android.compose.animation.scene.transformation.Fade import com.android.compose.animation.scene.transformation.OverscrollTranslate import com.android.compose.animation.scene.transformation.PropertyTransformation -import com.android.compose.animation.scene.transformation.RangedPropertyTransformation import com.android.compose.animation.scene.transformation.ScaleSize import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.animation.scene.transformation.Transformation +import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.animation.scene.transformation.Translate /** The transitions configuration of a [SceneTransitionLayout]. */ @@ -233,7 +233,7 @@ interface TransformationSpec { val distance: UserActionDistance? /** The list of [Transformation] applied to elements during this transition. */ - val transformations: List<Transformation> + val transformations: List<TransformationWithRange<*>> companion object { internal val Empty = @@ -325,7 +325,7 @@ internal class TransformationSpecImpl( override val progressSpec: AnimationSpec<Float>, override val swipeSpec: SpringSpec<Float>?, override val distance: UserActionDistance?, - override val transformations: List<Transformation>, + override val transformations: List<TransformationWithRange<*>>, ) : TransformationSpec { private val cache = mutableMapOf<ElementKey, MutableMap<ContentKey, ElementTransformations>>() @@ -340,59 +340,65 @@ internal class TransformationSpecImpl( element: ElementKey, content: ContentKey, ): ElementTransformations { - var shared: SharedElementTransformation? = null - var offset: PropertyTransformation<Offset>? = null - var size: PropertyTransformation<IntSize>? = null - var drawScale: PropertyTransformation<Scale>? = null - var alpha: PropertyTransformation<Float>? = null - - fun <T> onPropertyTransformation( - root: PropertyTransformation<T>, - current: PropertyTransformation<T> = root, - ) { - when (current) { + var shared: TransformationWithRange<SharedElementTransformation>? = null + var offset: TransformationWithRange<PropertyTransformation<Offset>>? = null + var size: TransformationWithRange<PropertyTransformation<IntSize>>? = null + var drawScale: TransformationWithRange<PropertyTransformation<Scale>>? = null + var alpha: TransformationWithRange<PropertyTransformation<Float>>? = null + + transformations.fastForEach { transformationWithRange -> + val transformation = transformationWithRange.transformation + if (!transformation.matcher.matches(element, content)) { + return@fastForEach + } + + when (transformation) { + is SharedElementTransformation -> { + throwIfNotNull(shared, element, name = "shared") + shared = + transformationWithRange + as TransformationWithRange<SharedElementTransformation> + } is Translate, is OverscrollTranslate, is EdgeTranslate, is AnchoredTranslate -> { throwIfNotNull(offset, element, name = "offset") - offset = root as PropertyTransformation<Offset> + offset = + transformationWithRange + as TransformationWithRange<PropertyTransformation<Offset>> } is ScaleSize, is AnchoredSize -> { throwIfNotNull(size, element, name = "size") - size = root as PropertyTransformation<IntSize> + size = + transformationWithRange + as TransformationWithRange<PropertyTransformation<IntSize>> } is DrawScale -> { throwIfNotNull(drawScale, element, name = "drawScale") - drawScale = root as PropertyTransformation<Scale> + drawScale = + transformationWithRange + as TransformationWithRange<PropertyTransformation<Scale>> } is Fade -> { throwIfNotNull(alpha, element, name = "alpha") - alpha = root as PropertyTransformation<Float> - } - is RangedPropertyTransformation -> onPropertyTransformation(root, current.delegate) - } - } - - transformations.fastForEach { transformation -> - if (!transformation.matcher.matches(element, content)) { - return@fastForEach - } - - when (transformation) { - is SharedElementTransformation -> { - throwIfNotNull(shared, element, name = "shared") - shared = transformation + alpha = + transformationWithRange + as TransformationWithRange<PropertyTransformation<Float>> } - is PropertyTransformation<*> -> onPropertyTransformation(transformation) + else -> error("Unknown transformation: $transformation") } } return ElementTransformations(shared, offset, size, drawScale, alpha) } - private fun throwIfNotNull(previous: Transformation?, element: ElementKey, name: String) { + private fun throwIfNotNull( + previous: TransformationWithRange<*>?, + element: ElementKey, + name: String, + ) { if (previous != null) { error("$element has multiple $name transformations") } @@ -401,9 +407,9 @@ internal class TransformationSpecImpl( /** The transformations of an element during a transition. */ internal class ElementTransformations( - val shared: SharedElementTransformation?, - val offset: PropertyTransformation<Offset>?, - val size: PropertyTransformation<IntSize>?, - val drawScale: PropertyTransformation<Scale>?, - val alpha: PropertyTransformation<Float>?, + val shared: TransformationWithRange<SharedElementTransformation>?, + val offset: TransformationWithRange<PropertyTransformation<Offset>>?, + val size: TransformationWithRange<PropertyTransformation<IntSize>>?, + val drawScale: TransformationWithRange<PropertyTransformation<Scale>>?, + val alpha: TransformationWithRange<PropertyTransformation<Float>>?, ) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index 269d91b02e7d..e461f9ccc295 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -34,12 +34,11 @@ import com.android.compose.animation.scene.transformation.DrawScale import com.android.compose.animation.scene.transformation.EdgeTranslate import com.android.compose.animation.scene.transformation.Fade import com.android.compose.animation.scene.transformation.OverscrollTranslate -import com.android.compose.animation.scene.transformation.PropertyTransformation -import com.android.compose.animation.scene.transformation.RangedPropertyTransformation import com.android.compose.animation.scene.transformation.ScaleSize import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.animation.scene.transformation.Transformation import com.android.compose.animation.scene.transformation.TransformationRange +import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.animation.scene.transformation.Translate internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions { @@ -158,7 +157,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { } internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { - val transformations = mutableListOf<Transformation>() + val transformations = mutableListOf<TransformationWithRange<*>>() private var range: TransformationRange? = null protected var reversed = false override var distance: UserActionDistance? = null @@ -174,19 +173,13 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { range = null } - protected fun transformation(transformation: PropertyTransformation<*>) { - val transformation = - if (range != null) { - RangedPropertyTransformation(transformation, range!!) - } else { - transformation - } - + protected fun transformation(transformation: Transformation) { + val transformationWithRange = TransformationWithRange(transformation, range) transformations.add( if (reversed) { - transformation.reversed() + transformationWithRange.reversed() } else { - transformation + transformationWithRange } ) } @@ -264,7 +257,7 @@ internal class TransitionBuilderImpl(override val transition: TransitionState.Tr "(${transition.toContent.debugName})" } - transformations.add(SharedElementTransformation(matcher, enabled, elevateInContent)) + transformation(SharedElementTransformation(matcher, enabled, elevateInContent)) } override fun timestampRange( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt index 5936d2595465..0ddeb7c7445f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt @@ -33,7 +33,7 @@ internal class AnchoredSize( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: IntSize, + idleValue: IntSize, ): IntSize { fun anchorSizeIn(content: ContentKey): IntSize { val size = @@ -45,8 +45,8 @@ internal class AnchoredSize( ) return IntSize( - width = if (anchorWidth) size.width else value.width, - height = if (anchorHeight) size.height else value.height, + width = if (anchorWidth) size.width else idleValue.width, + height = if (anchorHeight) size.height else idleValue.height, ) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt index 0a59dfe515fc..47508b41633c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt @@ -31,7 +31,7 @@ internal class AnchoredTranslate( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Offset, + idleValue: Offset, ): Offset { fun throwException(content: ContentKey?): Nothing { throwMissingAnchorException( @@ -51,9 +51,9 @@ internal class AnchoredTranslate( val offset = anchorToOffset - anchorFromOffset return if (content == transition.toContent) { - Offset(value.x - offset.x, value.y - offset.y) + Offset(idleValue.x - offset.x, idleValue.y - offset.y) } else { - Offset(value.x + offset.x, value.y + offset.y) + Offset(idleValue.x + offset.x, idleValue.y + offset.y) } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt index 7223dad43a2e..8488ae5178b0 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt @@ -33,12 +33,11 @@ internal class DrawScale( private val scaleY: Float, private val pivot: Offset = Offset.Unspecified, ) : PropertyTransformation<Scale> { - override fun PropertyTransformationScope.transform( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Scale, + idleValue: Scale, ): Scale { return Scale(scaleX, scaleY, pivot) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt index 4ae07c541011..884aae4b8b1a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt @@ -33,37 +33,37 @@ internal class EdgeTranslate( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Offset, + idleValue: Offset, ): Offset { val sceneSize = content.targetSize() ?: error("Content ${content.debugName} does not have a target size") - val elementSize = element.targetSize(content) ?: return value + val elementSize = element.targetSize(content) ?: return idleValue return when (edge.resolve(layoutDirection)) { Edge.Resolved.Top -> if (startsOutsideLayoutBounds) { - Offset(value.x, -elementSize.height.toFloat()) + Offset(idleValue.x, -elementSize.height.toFloat()) } else { - Offset(value.x, 0f) + Offset(idleValue.x, 0f) } Edge.Resolved.Left -> if (startsOutsideLayoutBounds) { - Offset(-elementSize.width.toFloat(), value.y) + Offset(-elementSize.width.toFloat(), idleValue.y) } else { - Offset(0f, value.y) + Offset(0f, idleValue.y) } Edge.Resolved.Bottom -> if (startsOutsideLayoutBounds) { - Offset(value.x, sceneSize.height.toFloat()) + Offset(idleValue.x, sceneSize.height.toFloat()) } else { - Offset(value.x, (sceneSize.height - elementSize.height).toFloat()) + Offset(idleValue.x, (sceneSize.height - elementSize.height).toFloat()) } Edge.Resolved.Right -> if (startsOutsideLayoutBounds) { - Offset(sceneSize.width.toFloat(), value.y) + Offset(sceneSize.width.toFloat(), idleValue.y) } else { - Offset((sceneSize.width - elementSize.width).toFloat(), value.y) + Offset((sceneSize.width - elementSize.width).toFloat(), idleValue.y) } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt index c11ec977fe2b..ef769e7d0c19 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt @@ -27,7 +27,7 @@ internal class Fade(override val matcher: ElementMatcher) : PropertyTransformati content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Float, + idleValue: Float, ): Float { // Return the alpha value of [element] either when it starts fading in or when it finished // fading out, which is `0` in both cases. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt index a159a5b5b2bd..ef3654b65b0a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt @@ -36,11 +36,11 @@ internal class ScaleSize( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: IntSize, + idleValue: IntSize, ): IntSize { return IntSize( - width = (value.width * width).roundToInt(), - height = (value.height * height).roundToInt(), + width = (idleValue.width * width).roundToInt(), + height = (idleValue.height * height).roundToInt(), ) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt index d38067d9af38..74a3ead3fbd7 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt @@ -36,14 +36,6 @@ sealed interface Transformation { */ val matcher: ElementMatcher - /** - * The range during which the transformation is applied. If it is `null`, then the - * transformation will be applied throughout the whole scene transition. - */ - // TODO(b/240432457): Move this back to PropertyTransformation. - val range: TransformationRange? - get() = null - /* * Reverse this transformation. This is called when we use Transition(from = A, to = B) when * animating from B to A and there is no Transition(from = B, to = A) defined. @@ -66,14 +58,14 @@ interface PropertyTransformation<T> : Transformation { * - the value at progress = 100% for elements that are leaving the layout (i.e. elements in the * content we are transitioning from). * - * The returned value will be interpolated using the [transition] progress and [value], the + * The returned value will be interpolated using the [transition] progress and [idleValue], the * value of the property when we are idle. */ fun PropertyTransformationScope.transform( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: T, + idleValue: T, ): T } @@ -82,20 +74,15 @@ interface PropertyTransformationScope : Density, ElementStateScope { val layoutDirection: LayoutDirection } -/** - * A [PropertyTransformation] associated to a range. This is a helper class so that normal - * implementations of [PropertyTransformation] don't have to take care of reversing their range when - * they are reversed. - */ -internal class RangedPropertyTransformation<T>( - val delegate: PropertyTransformation<T>, - override val range: TransformationRange, -) : PropertyTransformation<T> by delegate { - override fun reversed(): Transformation { - return RangedPropertyTransformation( - delegate.reversed() as PropertyTransformation<T>, - range.reversed(), - ) +/** A pair consisting of a [transformation] and optional [range]. */ +class TransformationWithRange<out T : Transformation>( + val transformation: T, + val range: TransformationRange?, +) { + fun reversed(): TransformationWithRange<T> { + if (range == null) return this + + return TransformationWithRange(transformation = transformation, range = range.reversed()) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt index af0a6edfa2fb..356ed9969458 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt @@ -35,9 +35,9 @@ internal class Translate( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Offset, + idleValue: Offset, ): Offset { - return Offset(value.x + x.toPx(), value.y + y.toPx()) + return Offset(idleValue.x + x.toPx(), idleValue.y + y.toPx()) } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt index d66d6b3ab219..d31711496ff0 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt @@ -28,8 +28,8 @@ import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transformation.OverscrollTranslate -import com.android.compose.animation.scene.transformation.Transformation import com.android.compose.animation.scene.transformation.TransformationRange +import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.test.transition import com.google.common.truth.Correspondence import com.google.common.truth.Truth.assertThat @@ -310,7 +310,8 @@ class TransitionDslTest { } val overscrollSpec = transitions.overscrollSpecs.single() - val transformation = overscrollSpec.transformationSpec.transformations.single() + val transformation = + overscrollSpec.transformationSpec.transformations.single().transformation assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java) } @@ -344,7 +345,7 @@ class TransitionDslTest { companion object { private val TRANSFORMATION_RANGE = - Correspondence.transforming<Transformation, TransformationRange?>( + Correspondence.transforming<TransformationWithRange<*>, TransformationRange?>( { it?.range }, "has range equal to", ) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt index 0b55a6e3ffa1..d86c0d664590 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt @@ -39,7 +39,7 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me val digitLeftTopMap = mutableMapOf<Int, Point>() var maxSingleDigitSize = Point(-1, -1) val lockscreenTranslate = Point(0, 0) - val aodTranslate = Point(0, 0) + var aodTranslate = Point(0, 0) init { setWillNotDraw(false) @@ -64,8 +64,7 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me maxSingleDigitSize.y = max(maxSingleDigitSize.y, textView.measuredHeight) } val textView = digitalClockTextViewMap[R.id.HOUR_FIRST_DIGIT]!! - aodTranslate.x = -(maxSingleDigitSize.x * AOD_HORIZONTAL_TRANSLATE_RATIO).toInt() - aodTranslate.y = (maxSingleDigitSize.y * AOD_VERTICAL_TRANSLATE_RATIO).toInt() + aodTranslate = Point(0, 0) return Point( ((maxSingleDigitSize.x + abs(aodTranslate.x)) * 2), ((maxSingleDigitSize.y + abs(aodTranslate.y)) * 2), @@ -162,9 +161,6 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me val AOD_TRANSITION_DURATION = 750L val CHARGING_TRANSITION_DURATION = 300L - val AOD_HORIZONTAL_TRANSLATE_RATIO = 0.15F - val AOD_VERTICAL_TRANSLATE_RATIO = 0.075F - // Use the sign of targetTranslation to control the direction of digit translation fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point { val outPoint = Point(targetTranslation) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt index c0899e3006a6..5c84f2d04ccc 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -148,7 +148,11 @@ open class SimpleDigitalClockTextView( lsFontVariation = ClockFontAxisSetting.toFVar(axes + OPTICAL_SIZE_AXIS) lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation) typeface = lockScreenPaint.typeface - textAnimator.setTextStyle(fvar = lsFontVariation, animate = true) + + lockScreenPaint.getTextBounds(text, 0, text.length, textBounds) + targetTextBounds.set(textBounds) + + textAnimator.setTextStyle(fvar = lsFontVariation, animate = false) measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED) recomputeMaxSingleDigitSizes() requestLayout() @@ -201,7 +205,7 @@ open class SimpleDigitalClockTextView( } else { textBounds.height() + 2 * lockScreenPaint.strokeWidth.toInt() }, - MeasureSpec.getMode(measuredHeight), + MeasureSpec.getMode(measuredHeightAndState), ) } @@ -215,10 +219,10 @@ open class SimpleDigitalClockTextView( } else { max( textBounds.width() + 2 * lockScreenPaint.strokeWidth.toInt(), - MeasureSpec.getSize(measuredWidth), + MeasureSpec.getSize(measuredWidthAndState), ) }, - MeasureSpec.getMode(measuredWidth), + MeasureSpec.getMode(measuredWidthAndState), ) } diff --git a/packages/SystemUI/docs/demo_mode.md b/packages/SystemUI/docs/demo_mode.md index ade5171d6415..c18d1f143b85 100644 --- a/packages/SystemUI/docs/demo_mode.md +++ b/packages/SystemUI/docs/demo_mode.md @@ -35,6 +35,7 @@ Commands are sent as string extras with key ```command``` (required). Possible v | | ```fully``` | | Sets MCS state to fully connected (```true```, ```false```) | | ```wifi``` | | ```show``` to show icon, any other value to hide | | | ```level``` | Sets wifi level (null or 0-4) +| | | ```hotspot``` | Sets the wifi to be from an Instant Hotspot. Values: ```none```, ```unknown```, ```phone```, ```tablet```, ```laptop```, ```watch```, ```auto```. (See `DemoModeWifiDataSource.kt`.) | | ```mobile``` | | ```show``` to show icon, any other value to hide | | | ```datatype``` | Values: ```1x```, ```3g```, ```4g```, ```e```, ```g```, ```h```, ```lte```, ```roam```, any other value to hide | | | ```level``` | Sets mobile signal strength level (null or 0-4) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java index c74d340ee325..b087ecf1a488 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java @@ -19,7 +19,6 @@ package com.android.systemui.accessibility; import static com.android.systemui.accessibility.MagnificationImpl.DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -190,8 +189,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Test public void showMagnificationButton_delayedShowButton() throws RemoteException { - // magnification settings panel should not be showing - assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); + when(mMagnificationSettingsController.isMagnificationSettingsShowing()).thenReturn(false); mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); @@ -237,8 +235,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Test public void removeMagnificationButton_delayingShowButton_doNotShowButtonAfterTimeout() throws RemoteException { - // magnification settings panel should not be showing - assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); + when(mMagnificationSettingsController.isMagnificationSettingsShowing()).thenReturn(false); mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS new file mode 100644 index 000000000000..a2001e66e55b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS @@ -0,0 +1 @@ +include /core/java/android/view/accessibility/OWNERS
\ No newline at end of file diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 25696bffdd66..f41d5c8eeb23 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; -import static android.view.Choreographer.FrameCallback; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowInsets.Type.systemGestures; @@ -28,14 +27,8 @@ import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityActi import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.hasItems; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.AdditionalAnswers.returnsSecondArg; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; @@ -45,13 +38,17 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static java.util.Arrays.asList; + import android.animation.ValueAnimator; import android.annotation.IdRes; import android.annotation.Nullable; @@ -63,38 +60,36 @@ import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PointF; import android.graphics.Rect; -import android.graphics.Region; -import android.graphics.RegionIterator; import android.os.Handler; import android.os.RemoteException; import android.os.SystemClock; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.TestableLooper; import android.testing.TestableResources; -import android.text.TextUtils; import android.util.Size; +import android.view.AttachedSurfaceControl; import android.view.Display; -import android.view.IWindowSession; +import android.view.Gravity; import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowManager; -import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IRemoteMagnificationAnimationCallback; +import android.widget.FrameLayout; +import android.window.InputTransferToken; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; -import com.android.app.viewcapture.ViewCapture; -import com.android.app.viewcapture.ViewCaptureAwareWindowManager; -import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.AnimatorTestRule; @@ -109,8 +104,6 @@ import com.android.systemui.utils.os.FakeHandler; import com.google.common.util.concurrent.AtomicDouble; -import kotlin.Lazy; - import org.junit.After; import org.junit.Assume; import org.junit.Before; @@ -118,31 +111,27 @@ import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; @LargeTest @TestableLooper.RunWithLooper @RunWith(AndroidJUnit4.class) -@RequiresFlagsDisabled(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER) public class WindowMagnificationControllerTest extends SysuiTestCase { @Rule // NOTE: pass 'null' to allow this test advances time on the main thread. - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(null); - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null); private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000; @Mock - private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; - @Mock private MirrorWindowControl mMirrorWindowControl; @Mock private WindowMagnifierCallback mWindowMagnifierCallback; @@ -150,12 +139,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { IRemoteMagnificationAnimationCallback mAnimationCallback; @Mock IRemoteMagnificationAnimationCallback mAnimationCallback2; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + + private SurfaceControl.Transaction mTransaction; @Mock private SecureSettings mSecureSettings; - @Mock - private Lazy<ViewCapture> mLazyViewCapture; private long mWaitAnimationDuration; private long mWaitBounceEffectDuration; @@ -170,11 +157,17 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0); private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); - private IWindowSession mWindowSessionSpy; - private View mSpyView; private View.OnTouchListener mTouchListener; + private MotionEventHelper mMotionEventHelper = new MotionEventHelper(); + + // This list contains all SurfaceControlViewHosts created during a given test. If the + // magnification window is recreated during a test, the list will contain more than a single + // element. + private List<SurfaceControlViewHost> mSurfaceControlViewHosts = new ArrayList<>(); + // The most recently created SurfaceControlViewHost. + private SurfaceControlViewHost mSurfaceControlViewHost; private KosmosJavaAdapter mKosmos; private FakeSharedPreferences mSharedPreferences; @@ -196,15 +189,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final WindowManager wm = mContext.getSystemService(WindowManager.class); mWindowManager = spy(new TestableWindowManager(wm)); - mWindowSessionSpy = spy(WindowManagerGlobal.getWindowSession()); - mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); - doAnswer(invocation -> { - FrameCallback callback = invocation.getArgument(0); - callback.doFrame(0); - return null; - }).when(mSfVsyncFrameProvider).postFrameCallback( - any(FrameCallback.class)); mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin()); mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class)); when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then( @@ -228,13 +213,20 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( mContext, mValueAnimator); + Supplier<SurfaceControlViewHost> scvhSupplier = () -> { + mSurfaceControlViewHost = spy(new SurfaceControlViewHost( + mContext, mContext.getDisplay(), new InputTransferToken(), + "WindowMagnification")); + ViewRootImpl viewRoot = mock(ViewRootImpl.class); + when(mSurfaceControlViewHost.getRootSurfaceControl()).thenReturn(viewRoot); + mSurfaceControlViewHosts.add(mSurfaceControlViewHost); + return mSurfaceControlViewHost; + }; + mTransaction = spy(new SurfaceControl.Transaction()); mSharedPreferences = new FakeSharedPreferences(); when(mContext.getSharedPreferences( eq("window_magnification_preferences"), anyInt())) .thenReturn(mSharedPreferences); - ViewCaptureAwareWindowManager viewCaptureAwareWindowManager = new - ViewCaptureAwareWindowManager(mWindowManager, mLazyViewCapture, - /* isViewCaptureEnabled= */ false); mWindowMagnificationController = new WindowMagnificationController( mContext, @@ -245,10 +237,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnifierCallback, mSysUiState, mSecureSettings, - /* scvhSupplier= */ () -> null, - mSfVsyncFrameProvider, - /* globalWindowSessionSupplier= */ () -> mWindowSessionSpy, - viewCaptureAwareWindowManager); + scvhSupplier); verify(mMirrorWindowControl).setWindowDelegate( any(MirrorWindowControl.MirrorWindowDelegate.class)); @@ -277,7 +266,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { verify(mSecureSettings).getIntForUser( eq(Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING), /* def */ eq(1), /* userHandle= */ anyInt()); - assertTrue(mWindowMagnificationController.isDiagonalScrollingEnabled()); + assertThat(mWindowMagnificationController.isDiagonalScrollingEnabled()).isTrue(); } @Test @@ -325,7 +314,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { }); advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS); - verify(mSfVsyncFrameProvider, atLeast(2)).postFrameCallback(any()); + verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(), + eq(Surface.ROTATION_0)); } @Test @@ -342,10 +332,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged( (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - assertEquals(mWindowMagnificationController.getCenterX(), - sourceBoundsCaptor.getValue().exactCenterX(), 0); - assertEquals(mWindowMagnificationController.getCenterY(), - sourceBoundsCaptor.getValue().exactCenterY(), 0); + assertThat(mWindowMagnificationController.getCenterX()) + .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX()); + assertThat(mWindowMagnificationController.getCenterY()) + .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY()); } @Test @@ -357,8 +347,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { // Wait for Rects updated. waitForIdleSync(); - List<Rect> rects = mWindowManager.getAttachedView().getSystemGestureExclusionRects(); - assertFalse(rects.isEmpty()); + List<Rect> rects = mSurfaceControlViewHost.getView().getSystemGestureExclusionRects(); + assertThat(rects).isNotEmpty(); } @Ignore("The default window size should be constrained after fixing b/288056772") @@ -373,11 +363,11 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { }); final int halfScreenSize = screenSize / 2; - WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView(); + ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); // The frame size should be the half of smaller value of window height/width unless it //exceed the max frame size. - assertTrue(params.width < halfScreenSize); - assertTrue(params.height < halfScreenSize); + assertThat(params.width).isLessThan(halfScreenSize); + assertThat(params.height).isLessThan(halfScreenSize); } @Test @@ -411,7 +401,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { }); verify(mMirrorWindowControl).destroyControl(); - assertFalse(hasMagnificationOverlapFlag()); + assertThat(hasMagnificationOverlapFlag()).isFalse(); } @Test @@ -435,10 +425,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); - mWindowMagnificationController.moveWindowMagnifier(100f, 100f); }); - verify(mSfVsyncFrameProvider, atLeastOnce()).postFrameCallback(any()); + waitForIdleSync(); + mInstrumentation.runOnMainSync( + () -> mWindowMagnificationController.moveWindowMagnifier(100f, 100f)); + + verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(), + eq(Surface.ROTATION_0)); } @Test @@ -455,6 +449,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10; final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10; + reset(mWindowMagnifierCallback); mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.moveWindowMagnifierToPosition( targetCenterX, targetCenterY, mAnimationCallback); @@ -465,12 +460,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { verify(mAnimationCallback, never()).onResult(eq(false)); verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - assertEquals(mWindowMagnificationController.getCenterX(), - sourceBoundsCaptor.getValue().exactCenterX(), 0); - assertEquals(mWindowMagnificationController.getCenterY(), - sourceBoundsCaptor.getValue().exactCenterY(), 0); - assertEquals(mWindowMagnificationController.getCenterX(), targetCenterX, 0); - assertEquals(mWindowMagnificationController.getCenterY(), targetCenterY, 0); + assertThat(mWindowMagnificationController.getCenterX()) + .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX()); + assertThat(mWindowMagnificationController.getCenterY()) + .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY()); + assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(targetCenterX); + assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(targetCenterY); } @Test @@ -487,6 +482,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final float centerX = sourceBoundsCaptor.getValue().exactCenterX(); final float centerY = sourceBoundsCaptor.getValue().exactCenterY(); + reset(mWindowMagnifierCallback); mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.moveWindowMagnifierToPosition( centerX + 10, centerY + 10, mAnimationCallback); @@ -505,12 +501,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { verify(mAnimationCallback, times(3)).onResult(eq(false)); verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - assertEquals(mWindowMagnificationController.getCenterX(), - sourceBoundsCaptor.getValue().exactCenterX(), 0); - assertEquals(mWindowMagnificationController.getCenterY(), - sourceBoundsCaptor.getValue().exactCenterY(), 0); - assertEquals(mWindowMagnificationController.getCenterX(), centerX + 40, 0); - assertEquals(mWindowMagnificationController.getCenterY(), centerY + 40, 0); + assertThat(mWindowMagnificationController.getCenterX()) + .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX()); + assertThat(mWindowMagnificationController.getCenterY()) + .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY()); + assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(centerX + 40); + assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(centerY + 40); } @Test @@ -521,10 +517,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f)); - assertEquals(3.0f, mWindowMagnificationController.getScale(), 0); - final View mirrorView = mWindowManager.getAttachedView(); - assertNotNull(mirrorView); - assertThat(mirrorView.getStateDescription().toString(), containsString("300")); + assertThat(mWindowMagnificationController.getScale()).isEqualTo(3.0f); + final View mirrorView = mSurfaceControlViewHost.getView(); + assertThat(mirrorView).isNotNull(); + assertThat(mirrorView.getStateDescription().toString()).contains("300"); } @Test @@ -563,12 +559,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged( ActivityInfo.CONFIG_ORIENTATION)); - assertEquals(newRotation, mWindowMagnificationController.mRotation); + assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation); final PointF expectedCenter = new PointF(magnifiedCenter.y, displayWidth - magnifiedCenter.x); final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(), mWindowMagnificationController.getCenterY()); - assertEquals(expectedCenter, actualCenter); + assertThat(actualCenter).isEqualTo(expectedCenter); } @Test @@ -583,7 +579,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged( ActivityInfo.CONFIG_ORIENTATION)); - assertEquals(newRotation, mWindowMagnificationController.mRotation); + assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation); } @Test @@ -610,14 +606,13 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { }); // The ratio of center to window size should be the same. - assertEquals(expectedRatio, - mWindowMagnificationController.getCenterX() / testWindowBounds.width(), - 0); - assertEquals(expectedRatio, - mWindowMagnificationController.getCenterY() / testWindowBounds.height(), - 0); + assertThat(mWindowMagnificationController.getCenterX() / testWindowBounds.width()) + .isEqualTo(expectedRatio); + assertThat(mWindowMagnificationController.getCenterY() / testWindowBounds.height()) + .isEqualTo(expectedRatio); } + @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) @Test public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() { int newSmallestScreenWidthDp = @@ -635,7 +630,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { Float.NaN); }); - // Change screen density and size to trigger restoring the preferred window size + // Screen density and size change mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp; final Rect testWindowBounds = new Rect( mWindowManager.getCurrentWindowMetrics().getBounds()); @@ -648,12 +643,56 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { // wait for rect update waitForIdleSync(); - WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView(); + ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( R.dimen.magnification_mirror_surface_margin); // The width and height of the view include the magnification frame and the margins. - assertTrue(params.width == (windowFrameSize + 2 * mirrorSurfaceMargin)); - assertTrue(params.height == (windowFrameSize + 2 * mirrorSurfaceMargin)); + assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); + assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); + } + + @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) + @Test + public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() { + int newSmallestScreenWidthDp = + mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; + int windowFrameSize = mResources.getDimensionPixelSize( + com.android.internal.R.dimen.accessibility_window_magnifier_min_size); + Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize); + mSharedPreferences + .edit() + .putString(String.valueOf(newSmallestScreenWidthDp), + WindowMagnificationFrameSpec.serialize( + WindowMagnificationSettings.MagnificationSize.CUSTOM, + preferredWindowSize)) + .commit(); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, + Float.NaN); + }); + + // Screen density and size change + mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp; + final Rect testWindowBounds = new Rect( + mWindowManager.getCurrentWindowMetrics().getBounds()); + testWindowBounds.set(testWindowBounds.left, testWindowBounds.top, + testWindowBounds.right + 100, testWindowBounds.bottom + 100); + mWindowManager.setWindowBounds(testWindowBounds); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); + }); + + // wait for rect update + waitForIdleSync(); + verify(mWindowMagnifierCallback).onWindowMagnifierBoundsRestored( + eq(mContext.getDisplayId()), + eq(WindowMagnificationSettings.MagnificationSize.CUSTOM)); + ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); + final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( + R.dimen.magnification_mirror_surface_margin); + // The width and height of the view include the magnification frame and the margins. + assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); + assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); } @Test @@ -675,10 +714,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final int defaultWindowSize = mWindowMagnificationController.getMagnificationWindowSizeFromIndex( WindowMagnificationSettings.MagnificationSize.MEDIUM); - WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView(); + ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); - assertTrue(params.width == defaultWindowSize); - assertTrue(params.height == defaultWindowSize); + assertThat(params.width).isEqualTo(defaultWindowSize); + assertThat(params.height).isEqualTo(defaultWindowSize); } @Test @@ -695,9 +734,9 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { }); verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt()); - verify(mWindowManager).removeView(any()); + verify(mSurfaceControlViewHosts.get(0)).release(); verify(mMirrorWindowControl).destroyControl(); - verify(mWindowManager).addView(any(), any()); + verify(mSurfaceControlViewHosts.get(1)).setView(any(), any()); verify(mMirrorWindowControl).showControl(); } @@ -716,21 +755,30 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN, Float.NaN); }); - final View mirrorView = mWindowManager.getAttachedView(); - assertNotNull(mirrorView); + final View mirrorView = mSurfaceControlViewHost.getView(); + assertThat(mirrorView).isNotNull(); final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo(); mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo); - assertNotNull(nodeInfo.getContentDescription()); - assertThat(nodeInfo.getStateDescription().toString(), containsString("250")); - assertThat(nodeInfo.getActionList(), - hasItems(new AccessibilityAction(R.id.accessibility_action_zoom_in, null), - new AccessibilityAction(R.id.accessibility_action_zoom_out, null), - new AccessibilityAction(R.id.accessibility_action_move_right, null), - new AccessibilityAction(R.id.accessibility_action_move_left, null), - new AccessibilityAction(R.id.accessibility_action_move_down, null), - new AccessibilityAction(R.id.accessibility_action_move_up, null))); + assertThat(nodeInfo.getContentDescription()).isNotNull(); + assertThat(nodeInfo.getStateDescription().toString()).contains("250"); + assertThat(nodeInfo.getActionList()).containsExactlyElementsIn(asList( + new AccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), + mContext.getResources().getString( + R.string.magnification_open_settings_click_label)), + new AccessibilityAction(R.id.accessibility_action_zoom_in, + mContext.getString(R.string.accessibility_control_zoom_in)), + new AccessibilityAction(R.id.accessibility_action_zoom_out, + mContext.getString(R.string.accessibility_control_zoom_out)), + new AccessibilityAction(R.id.accessibility_action_move_right, + mContext.getString(R.string.accessibility_control_move_right)), + new AccessibilityAction(R.id.accessibility_action_move_left, + mContext.getString(R.string.accessibility_control_move_left)), + new AccessibilityAction(R.id.accessibility_action_move_down, + mContext.getString(R.string.accessibility_control_move_down)), + new AccessibilityAction(R.id.accessibility_action_move_up, + mContext.getString(R.string.accessibility_control_move_up)))); } @Test @@ -741,29 +789,34 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { Float.NaN); }); - final View mirrorView = mWindowManager.getAttachedView(); - assertTrue( - mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null)); + final View mirrorView = mSurfaceControlViewHost.getView(); + assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null)) + .isTrue(); // Minimum scale is 1.0. verify(mWindowMagnifierCallback).onPerformScaleAction( eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true)); - assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null)); + assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null)) + .isTrue(); verify(mWindowMagnifierCallback).onPerformScaleAction( eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true)); // TODO: Verify the final state when the mirror surface is visible. - assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null)); - assertTrue( - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null)); - assertTrue( - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null)); - assertTrue( - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null)); + assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null)) + .isTrue(); + assertThat( + mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null)) + .isTrue(); + assertThat( + mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null)) + .isTrue(); + assertThat( + mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null)) + .isTrue(); verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId)); - assertTrue(mirrorView.performAccessibilityAction( - AccessibilityAction.ACTION_CLICK.getId(), null)); + assertThat(mirrorView.performAccessibilityAction( + AccessibilityAction.ACTION_CLICK.getId(), null)).isTrue(); verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId)); } @@ -775,7 +828,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { Float.NaN); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null); verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId)); @@ -795,20 +848,22 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { View topRightCorner = getInternalView(R.id.top_right_corner); View topLeftCorner = getInternalView(R.id.top_left_corner); - assertEquals(View.VISIBLE, closeButton.getVisibility()); - assertEquals(View.VISIBLE, bottomRightCorner.getVisibility()); - assertEquals(View.VISIBLE, bottomLeftCorner.getVisibility()); - assertEquals(View.VISIBLE, topRightCorner.getVisibility()); - assertEquals(View.VISIBLE, topLeftCorner.getVisibility()); + assertThat(closeButton.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(topRightCorner.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(topLeftCorner.getVisibility()).isEqualTo(View.VISIBLE); - final View mirrorView = mWindowManager.getAttachedView(); - mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), null); + final View mirrorView = mSurfaceControlViewHost.getView(); + mInstrumentation.runOnMainSync(() -> + mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), + null)); - assertEquals(View.GONE, closeButton.getVisibility()); - assertEquals(View.GONE, bottomRightCorner.getVisibility()); - assertEquals(View.GONE, bottomLeftCorner.getVisibility()); - assertEquals(View.GONE, topRightCorner.getVisibility()); - assertEquals(View.GONE, topLeftCorner.getVisibility()); + assertThat(closeButton.getVisibility()).isEqualTo(View.GONE); + assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.GONE); + assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.GONE); + assertThat(topRightCorner.getVisibility()).isEqualTo(View.GONE); + assertThat(topLeftCorner.getVisibility()).isEqualTo(View.GONE); } @Test @@ -828,7 +883,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AtomicInteger actualWindowHeight = new AtomicInteger(); final AtomicInteger actualWindowWidth = new AtomicInteger(); @@ -836,8 +891,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> { mirrorView.performAccessibilityAction( R.id.accessibility_action_increase_window_width, null); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( @@ -847,8 +904,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { int newWindowWidth = (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount)) + 2 * mirrorSurfaceMargin; - assertEquals(newWindowWidth, actualWindowWidth.get()); - assertEquals(startingHeight, actualWindowHeight.get()); + assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth); + assertThat(actualWindowHeight.get()).isEqualTo(startingHeight); } @Test @@ -868,7 +925,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AtomicInteger actualWindowHeight = new AtomicInteger(); final AtomicInteger actualWindowWidth = new AtomicInteger(); @@ -876,8 +933,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> { mirrorView.performAccessibilityAction( R.id.accessibility_action_increase_window_height, null); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( @@ -887,8 +946,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { int newWindowHeight = (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount)) + 2 * mirrorSurfaceMargin; - assertEquals(startingWidth, actualWindowWidth.get()); - assertEquals(newWindowHeight, actualWindowHeight.get()); + assertThat(actualWindowWidth.get()).isEqualTo(startingWidth); + assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight); } @Test @@ -904,11 +963,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AccessibilityNodeInfo accessibilityNodeInfo = mirrorView.createAccessibilityNodeInfo(); - assertFalse(accessibilityNodeInfo.getActionList().contains( - new AccessibilityAction(R.id.accessibility_action_increase_window_width, null))); + assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( + new AccessibilityAction( + R.id.accessibility_action_increase_window_width, + mContext.getString( + R.string.accessibility_control_increase_window_width))); } @Test @@ -924,11 +986,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AccessibilityNodeInfo accessibilityNodeInfo = mirrorView.createAccessibilityNodeInfo(); - assertFalse(accessibilityNodeInfo.getActionList().contains( - new AccessibilityAction(R.id.accessibility_action_increase_window_height, null))); + assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( + new AccessibilityAction( + R.id.accessibility_action_increase_window_height, null)); } @Test @@ -947,7 +1010,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AtomicInteger actualWindowHeight = new AtomicInteger(); final AtomicInteger actualWindowWidth = new AtomicInteger(); @@ -955,8 +1018,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> { mirrorView.performAccessibilityAction( R.id.accessibility_action_decrease_window_width, null); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( @@ -966,8 +1031,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { int newWindowWidth = (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount)) + 2 * mirrorSurfaceMargin; - assertEquals(newWindowWidth, actualWindowWidth.get()); - assertEquals(startingSize, actualWindowHeight.get()); + assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth); + assertThat(actualWindowHeight.get()).isEqualTo(startingSize); } @Test @@ -987,7 +1052,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AtomicInteger actualWindowHeight = new AtomicInteger(); final AtomicInteger actualWindowWidth = new AtomicInteger(); @@ -995,8 +1060,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> { mirrorView.performAccessibilityAction( R.id.accessibility_action_decrease_window_height, null); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( @@ -1006,8 +1073,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { int newWindowHeight = (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount)) + 2 * mirrorSurfaceMargin; - assertEquals(startingSize, actualWindowWidth.get()); - assertEquals(newWindowHeight, actualWindowHeight.get()); + assertThat(actualWindowWidth.get()).isEqualTo(startingSize); + assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight); } @Test @@ -1023,15 +1090,16 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AccessibilityNodeInfo accessibilityNodeInfo = mirrorView.createAccessibilityNodeInfo(); - assertFalse(accessibilityNodeInfo.getActionList().contains( - new AccessibilityAction(R.id.accessibility_action_decrease_window_width, null))); + assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( + new AccessibilityAction( + R.id.accessibility_action_decrease_window_width, null)); } @Test - public void windowHeightIsMin_noDecreaseWindowHeightA11yAcyion() { + public void windowHeightIsMin_noDecreaseWindowHeightA11yAction() { int mMinWindowSize = mResources.getDimensionPixelSize( com.android.internal.R.dimen.accessibility_window_magnifier_min_size); final int startingSize = mMinWindowSize; @@ -1043,11 +1111,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AccessibilityNodeInfo accessibilityNodeInfo = mirrorView.createAccessibilityNodeInfo(); - assertFalse(accessibilityNodeInfo.getActionList().contains( - new AccessibilityAction(R.id.accessibility_action_decrease_window_height, null))); + assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( + new AccessibilityAction( + R.id.accessibility_action_decrease_window_height, null)); } @Test @@ -1057,8 +1126,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { Float.NaN); }); - assertEquals(getContext().getResources().getString( - com.android.internal.R.string.android_system_label), getAccessibilityWindowTitle()); + assertThat(getAccessibilityWindowTitle()).isEqualTo(getContext().getResources().getString( + com.android.internal.R.string.android_system_label)); } @Test @@ -1073,14 +1142,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { Float.NaN); }); - assertEquals(Float.NaN, mWindowMagnificationController.getScale(), 0); + assertThat(mWindowMagnificationController.getScale()).isEqualTo(Float.NaN); } @Test public void enableWindowMagnification_rotationIsChanged_updateRotationValue() { // the config orientation should not be undefined, since it would cause config.diff // returning 0 and thus the orientation changed would not be detected - assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation); + assertThat(mResources.getConfiguration().orientation).isNotEqualTo(ORIENTATION_UNDEFINED); final Configuration config = mResources.getConfiguration(); config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT @@ -1091,7 +1160,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN)); - assertEquals(newRotation, mWindowMagnificationController.mRotation); + assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation); } @Test @@ -1119,7 +1188,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE); }); - assertTrue(TextUtils.equals(newA11yWindowTitle, getAccessibilityWindowTitle())); + assertThat(getAccessibilityWindowTitle()).isEqualTo(newA11yWindowTitle); } @Ignore("it's flaky in presubmit but works in abtd, filter for now. b/305654925") @@ -1134,7 +1203,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.onSingleTap(mSpyView); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AtomicDouble maxScaleX = new AtomicDouble(); advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> { @@ -1142,10 +1211,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max); final double oldMax = maxScaleX.get(); final double newMax = Math.max(mirrorView.getScaleX(), oldMax); - assertTrue(maxScaleX.compareAndSet(oldMax, newMax)); + assertThat(maxScaleX.compareAndSet(oldMax, newMax)).isTrue(); }); - assertTrue(maxScaleX.get() > 1.0); + assertThat(maxScaleX.get()).isGreaterThan(1.0); } @Test @@ -1174,30 +1243,23 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.updateWindowMagnificationInternal( Float.NaN, Float.NaN, Float.NaN); }); + // Wait for Region updated. + waitForIdleSync(); mInstrumentation.runOnMainSync( () -> { mWindowMagnificationController.moveWindowMagnifier(bounds.width(), 0); }); - // Wait for Region updated. waitForIdleSync(); - final ArgumentCaptor<Region> tapExcludeRegionCapturer = - ArgumentCaptor.forClass(Region.class); - verify(mWindowSessionSpy, times(2)) - .updateTapExcludeRegion(any(), tapExcludeRegionCapturer.capture()); - Region tapExcludeRegion = tapExcludeRegionCapturer.getValue(); - RegionIterator iterator = new RegionIterator(tapExcludeRegion); + AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl(); + // Verifying two times in: (1) enable window magnification (2) reposition drag handle + verify(viewRoot, times(2)).setTouchableRegion(any()); - final Rect topRect = new Rect(); - final Rect bottomRect = new Rect(); - assertTrue(iterator.next(topRect)); - assertTrue(iterator.next(bottomRect)); - assertFalse(iterator.next(new Rect())); - - assertEquals(topRect.right, bottomRect.right); - assertNotEquals(topRect.left, bottomRect.left); + View dragButton = getInternalView(R.id.drag_handle); + FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams(); + assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.LEFT); } @Test @@ -1210,29 +1272,23 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.updateWindowMagnificationInternal( Float.NaN, Float.NaN, Float.NaN); }); + // Wait for Region updated. + waitForIdleSync(); mInstrumentation.runOnMainSync( () -> { mWindowMagnificationController.moveWindowMagnifier(-bounds.width(), 0); }); - // Wait for Region updated. waitForIdleSync(); - final ArgumentCaptor<Region> tapExcludeRegionCapturer = - ArgumentCaptor.forClass(Region.class); - verify(mWindowSessionSpy).updateTapExcludeRegion(any(), tapExcludeRegionCapturer.capture()); - Region tapExcludeRegion = tapExcludeRegionCapturer.getValue(); - RegionIterator iterator = new RegionIterator(tapExcludeRegion); - - final Rect topRect = new Rect(); - final Rect bottomRect = new Rect(); - assertTrue(iterator.next(topRect)); - assertTrue(iterator.next(bottomRect)); - assertFalse(iterator.next(new Rect())); + AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl(); + // Verifying one times in: (1) enable window magnification + verify(viewRoot).setTouchableRegion(any()); - assertEquals(topRect.left, bottomRect.left); - assertNotEquals(topRect.right, bottomRect.right); + View dragButton = getInternalView(R.id.drag_handle); + FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams(); + assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.RIGHT); } @Test @@ -1249,13 +1305,13 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final AtomicInteger actualWindowWidth = new AtomicInteger(); mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(expectedWindowHeight, actualWindowHeight.get()); - assertEquals(expectedWindowWidth, actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight); + assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth); } @Test @@ -1271,12 +1327,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight); mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(expectedWindowHeight, actualWindowHeight.get()); - assertEquals(expectedWindowWidth, actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight); + assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth); } @Test @@ -1292,12 +1348,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.setWindowSize(minimumWindowSize - 10, minimumWindowSize - 10); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(minimumWindowSize, actualWindowHeight.get()); - assertEquals(minimumWindowSize, actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(minimumWindowSize); + assertThat(actualWindowWidth.get()).isEqualTo(minimumWindowSize); } @Test @@ -1311,12 +1367,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final AtomicInteger actualWindowWidth = new AtomicInteger(); mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.setWindowSize(bounds.width() + 10, bounds.height() + 10); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(bounds.height(), actualWindowHeight.get()); - assertEquals(bounds.width(), actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(bounds.height()); + assertThat(actualWindowWidth.get()).isEqualTo(bounds.width()); } @Test @@ -1342,12 +1398,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> { mWindowMagnificationController.changeMagnificationSize( WindowMagnificationSettings.MagnificationSize.LARGE); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(expectedWindowHeight, actualWindowHeight.get()); - assertEquals(expectedWindowWidth, actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight); + assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth); } @Test @@ -1376,12 +1434,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> { mWindowMagnificationController .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(startingSize + 1, actualWindowHeight.get()); - assertEquals(startingSize + 2, actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1); + assertThat(actualWindowWidth.get()).isEqualTo(startingSize + 2); } @Test @@ -1404,11 +1464,13 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); mWindowMagnificationController .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(startingSize + 1, actualWindowHeight.get()); - assertEquals(startingSize, actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1); + assertThat(actualWindowWidth.get()).isEqualTo(startingSize); } @Test @@ -1430,8 +1492,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { magnificationCenterY.set((int) mWindowMagnificationController.getCenterY()); }); - assertTrue(magnificationCenterX.get() < bounds.right); - assertTrue(magnificationCenterY.get() < bounds.bottom); + assertThat(magnificationCenterX.get()).isLessThan(bounds.right); + assertThat(magnificationCenterY.get()).isLessThan(bounds.bottom); } @Test @@ -1451,13 +1513,13 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { dragButton.dispatchTouchEvent( obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100)); - verify(mWindowManager).addView(any(View.class), any()); + verify(mSurfaceControlViewHost).setView(any(View.class), any()); } private <T extends View> T getInternalView(@IdRes int idRes) { - View mirrorView = mWindowManager.getAttachedView(); + View mirrorView = mSurfaceControlViewHost.getView(); T view = mirrorView.findViewById(idRes); - assertNotNull(view); + assertThat(view).isNotNull(); return view; } @@ -1466,14 +1528,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y); } - private CharSequence getAccessibilityWindowTitle() { - final View mirrorView = mWindowManager.getAttachedView(); + private String getAccessibilityWindowTitle() { + final View mirrorView = mSurfaceControlViewHost.getView(); if (mirrorView == null) { return null; } WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) mirrorView.getLayoutParams(); - return layoutParams.accessibilityTitle; + return layoutParams.accessibilityTitle.toString(); } private boolean hasMagnificationOverlapFlag() { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt index 3388a785a26a..20cd8608d204 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt @@ -28,11 +28,13 @@ import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD import com.android.systemui.SysuiTestCase import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.io.PrintWriter import java.io.Serializable @@ -68,6 +70,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { @Mock private lateinit var systemProperties: SystemPropertiesHelper @Mock private lateinit var resources: Resources @Mock private lateinit var restarter: Restarter + private lateinit var fakeExecutor: FakeExecutor private lateinit var userTracker: FakeUserTracker private val flagMap = mutableMapOf<String, Flag<*>>() private lateinit var broadcastReceiver: BroadcastReceiver @@ -83,6 +86,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA) flagMap.put(releasedFlagB.name, releasedFlagB) + fakeExecutor = FakeExecutor(FakeSystemClock()) userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockContext }) mFeatureFlagsClassicDebug = @@ -95,7 +99,8 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { serverFlagReader, flagMap, restarter, - userTracker + userTracker, + fakeExecutor, ) mFeatureFlagsClassicDebug.init() verify(flagManager).onSettingsChangedAction = any() @@ -325,14 +330,14 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { // trying to erase an id not in the map does nothing broadcastReceiver.onReceive( mockContext, - Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "") + Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, ""), ) verifyNoMoreInteractions(flagManager, globalSettings) // valid id with no value puts empty string in the setting broadcastReceiver.onReceive( mockContext, - Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1") + Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1"), ) verifyPutData("1", "", numReads = 0) } @@ -415,7 +420,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { serverFlagReader.setFlagValue( teamfoodableFlagA.namespace, teamfoodableFlagA.name, - !teamfoodableFlagA.default + !teamfoodableFlagA.default, ) verify(restarter, never()).restartSystemUI(anyString()) } @@ -428,7 +433,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { serverFlagReader.setFlagValue( teamfoodableFlagA.namespace, teamfoodableFlagA.name, - !teamfoodableFlagA.default + !teamfoodableFlagA.default, ) verify(restarter).restartSystemUI(anyString()) } @@ -441,7 +446,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { serverFlagReader.setFlagValue( teamfoodableFlagA.namespace, teamfoodableFlagA.name, - teamfoodableFlagA.default + teamfoodableFlagA.default, ) verify(restarter, never()).restartSystemUI(anyString()) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt index 7f313564e35a..2c12f8782ddc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt @@ -193,7 +193,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { @Test @EnableSceneContainer - fun surfaceBehindVisibility_fromLockscreenToGone_noUserInput_trueThroughout() = + fun surfaceBehindVisibility_fromLockscreenToGone_dependsOnDeviceEntry() = testScope.runTest { val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) @@ -208,7 +208,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { SuccessFingerprintAuthenticationStatus(0, true) ) - // Start the transition to Gone, the surface should become immediately visible. + // Start the transition to Gone, the surface should remain invisible. kosmos.setSceneTransition( ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, @@ -220,9 +220,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { ) ) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - assertThat(isSurfaceBehindVisible).isTrue() + assertThat(isSurfaceBehindVisible).isFalse() - // Towards the end of the transition, the surface should continue to be visible. + // Towards the end of the transition, the surface should continue to remain invisible. kosmos.setSceneTransition( ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, @@ -234,7 +234,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { ) ) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - assertThat(isSurfaceBehindVisible).isTrue() + assertThat(isSurfaceBehindVisible).isFalse() // After the transition, settles on Gone. Surface behind should stay visible now. kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone)) @@ -245,43 +245,6 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { @Test @EnableSceneContainer - fun surfaceBehindVisibility_fromLockscreenToGone_withUserInput_falseUntilInputStops() = - testScope.runTest { - val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) - val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) - - // Before the transition, we start on Lockscreen so the surface should start invisible. - kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Lockscreen)) - assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - assertThat(isSurfaceBehindVisible).isFalse() - - // Unlocked with fingerprint. - kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) - - // Start the transition to Gone, the surface should not be visible while - // isUserInputOngoing is true - val isUserInputOngoing = MutableStateFlow(true) - kosmos.setSceneTransition( - ObservableTransitionState.Transition( - fromScene = Scenes.Lockscreen, - toScene = Scenes.Gone, - isInitiatedByUserInput = true, - isUserInputOngoing = isUserInputOngoing, - progress = flowOf(0.51f), - currentScene = flowOf(Scenes.Gone), - ) - ) - assertThat(isSurfaceBehindVisible).isFalse() - - // When isUserInputOngoing becomes false, then the surface should become visible. - isUserInputOngoing.value = false - assertThat(isSurfaceBehindVisible).isTrue() - } - - @Test - @EnableSceneContainer fun surfaceBehindVisibility_fromBouncerToGone_becomesTrue() = testScope.runTest { val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index 7f0937059494..0e3b03f74c02 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -44,6 +44,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Ignore @@ -107,6 +108,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { fun translationAndScale_whenNotDozing() = testScope.runTest { val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to not dozing (on lockscreen) keyguardTransitionRepository.sendTransitionStep( @@ -180,6 +182,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { testScope.runTest { underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100)) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -221,6 +224,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { testScope.runTest { underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80)) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -263,6 +267,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { testScope.runTest { underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80)) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -305,6 +310,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -423,6 +429,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { .thenReturn(if (isWeatherClock) true else false) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -434,6 +441,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { ), validateStep = false, ) + runCurrent() // Trigger a change to the burn-in model burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt index e12c67b24893..104aa517fa4f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt @@ -16,7 +16,7 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.wm.shell.recents.RecentTasks -import com.android.wm.shell.shared.GroupedRecentTaskInfo +import com.android.wm.shell.shared.GroupedTaskInfo import com.android.wm.shell.shared.split.SplitBounds import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 import com.google.common.truth.Truth.assertThat @@ -219,9 +219,9 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { } @Suppress("UNCHECKED_CAST") - private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) { + private fun givenRecentTasks(vararg tasks: GroupedTaskInfo) { whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer { - val consumer = it.arguments.last() as Consumer<List<GroupedRecentTaskInfo>> + val consumer = it.arguments.last() as Consumer<List<GroupedTaskInfo>> consumer.accept(tasks.toList()) } } @@ -247,7 +247,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { userId: Int = 0, isVisible: Boolean = false, userType: RecentTask.UserType = STANDARD, - ): GroupedRecentTaskInfo { + ): GroupedTaskInfo { val userInfo = mock<UserInfo> { whenever(isCloneProfile).thenReturn(userType == CLONED) @@ -255,7 +255,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { whenever(isPrivateProfile).thenReturn(userType == PRIVATE) } whenever(userManager.getUserInfo(userId)).thenReturn(userInfo) - return GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, userId, isVisible)) + return GroupedTaskInfo.forFullscreenTasks(createTaskInfo(taskId, userId, isVisible)) } private fun createTaskPair( @@ -263,9 +263,9 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { userId1: Int = 0, taskId2: Int, userId2: Int = 0, - isVisible: Boolean = false - ): GroupedRecentTaskInfo = - GroupedRecentTaskInfo.forSplitTasks( + isVisible: Boolean = false, + ): GroupedTaskInfo = + GroupedTaskInfo.forSplitTasks( createTaskInfo(taskId1, userId1, isVisible), createTaskInfo(taskId2, userId2, isVisible), SplitBounds(Rect(), Rect(), taskId1, taskId2, SNAP_TO_2_50_50) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 2c8f7cf47723..55f88cc5b7a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -2119,40 +2119,6 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test - fun switchToGone_whenSurfaceBehindLockscreenVisibleMidTransition() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) - val transitionStateFlow = - prepareState(authenticationMethod = AuthenticationMethodModel.None) - underTest.start() - assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - // Swipe to Gone, more than halfway - transitionStateFlow.value = - ObservableTransitionState.Transition( - fromScene = Scenes.Lockscreen, - toScene = Scenes.Gone, - currentScene = flowOf(Scenes.Gone), - progress = flowOf(0.51f), - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(true), - ) - runCurrent() - // Lift finger - transitionStateFlow.value = - ObservableTransitionState.Transition( - fromScene = Scenes.Lockscreen, - toScene = Scenes.Gone, - currentScene = flowOf(Scenes.Gone), - progress = flowOf(0.51f), - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), - ) - runCurrent() - - assertThat(currentScene).isEqualTo(Scenes.Gone) - } - - @Test fun switchToGone_extendUnlock() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index 65005f840598..572f063c20c4 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -32,6 +32,34 @@ android:id="@+id/min_edge_guideline" app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing" android:orientation="vertical"/> + <!-- This toast-like indication layout was forked from text_toast.xml and will have the same + appearance as system toast. --> + <FrameLayout + android:id="@+id/indication_container" + android:visibility="gone" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:maxWidth="@*android:dimen/toast_width" + android:background="@android:drawable/toast_frame" + android:elevation="@*android:dimen/toast_elevation" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" + android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" + android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toBottomOf="parent"> + <TextView + android:id="@+id/indication_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="2" + android:paddingTop="12dp" + android:paddingBottom="12dp" + android:textAppearance="@*android:style/TextAppearance.Toast"/> + </FrameLayout> <!-- Negative horizontal margin because this container background must render beyond the thing it's constrained by (the actions themselves). --> <FrameLayout @@ -47,7 +75,7 @@ app:layout_constraintStart_toStartOf="@id/min_edge_guideline" app:layout_constraintTop_toTopOf="@id/actions_container" app:layout_constraintEnd_toEndOf="@id/actions_container" - app:layout_constraintBottom_toBottomOf="parent"/> + app:layout_constraintBottom_toTopOf="@id/indication_container"/> <HorizontalScrollView android:id="@+id/actions_container" android:layout_width="0dp" @@ -144,7 +172,7 @@ android:visibility="gone" android:elevation="7dp" android:padding="8dp" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/indication_container" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml index 9b3af52e9704..7c266e60b503 100644 --- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml +++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml @@ -65,7 +65,7 @@ android:background="@drawable/volume_drawer_selection_bg" android:contentDescription="@string/volume_ringer_change" android:gravity="center" - android:padding="10dp" + android:padding="@dimen/volume_dialog_ringer_horizontal_padding" android:src="@drawable/ic_volume_media" android:tint="?androidprv:attr/materialColorOnPrimary" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 1766cdf8c804..7b50582efe64 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1518,7 +1518,7 @@ <string name="no_unseen_notif_text">No new notifications</string> <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=60] --> - <string name="adaptive_notification_edu_hun_title">Notification cooldown is on</string> + <string name="adaptive_notification_edu_hun_title">Notification cooldown is now on</string> <!-- Text of heads up notification for adaptive notifications user education. [CHAR LIMIT=100] --> <string name="adaptive_notification_edu_hun_text">Your device volume and alerts are reduced automatically for up to 2 minutes when you get too many notifications at once.</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index 7ec977a8d6aa..9e8cabf141ed 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -243,10 +243,6 @@ public class Task { public Rect appBounds; - // Last snapshot data, only used for recent tasks - public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData = - new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData(); - @ViewDebug.ExportedProperty(category="recents") public boolean isVisible; @@ -283,7 +279,6 @@ public class Task { public Task(Task other) { this(other.key, other.colorPrimary, other.colorBackground, other.isDockable, other.isLocked, other.taskDescription, other.topActivity); - lastSnapshotData.set(other.lastSnapshotData); positionInParent = other.positionInParent; appBounds = other.appBounds; isVisible = other.isVisible; @@ -315,33 +310,10 @@ public class Task { : key.baseIntent.getComponent(); } - public void setLastSnapshotData(ActivityManager.RecentTaskInfo rawTask) { - lastSnapshotData.set(rawTask.lastSnapshotData); - } - public TaskKey getKey() { return key; } - /** - * Returns the visible width to height ratio. Returns 0f if snapshot data is not available. - */ - public float getVisibleThumbnailRatio(boolean clipInsets) { - if (lastSnapshotData.taskSize == null || lastSnapshotData.contentInsets == null) { - return 0f; - } - - float availableWidth = lastSnapshotData.taskSize.x; - float availableHeight = lastSnapshotData.taskSize.y; - if (clipInsets) { - availableWidth -= - (lastSnapshotData.contentInsets.left + lastSnapshotData.contentInsets.right); - availableHeight -= - (lastSnapshotData.contentInsets.top + lastSnapshotData.contentInsets.bottom); - } - return availableWidth / availableHeight; - } - @Override public boolean equals(Object o) { if (o == this) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 8b593701540b..eda07cfe8d91 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -121,6 +121,7 @@ import com.android.settingslib.WirelessUtils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; +import com.android.systemui.Flags; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; @@ -473,6 +474,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + @Deprecated private final SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray(); private final SparseBooleanArray mUserHasTrust = new SparseBooleanArray(); private final SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray(); @@ -2688,7 +2690,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @see Intent#ACTION_USER_UNLOCKED */ public boolean isUserUnlocked(int userId) { - return mUserIsUnlocked.get(userId); + if (Flags.userEncryptedSource()) { + boolean userStorageUnlocked = mUserManager.isUserUnlocked(userId); + mLogger.logUserStorageUnlocked(userId, userStorageUnlocked); + return userStorageUnlocked; + } else { + return mUserIsUnlocked.get(userId); + } } /** @@ -4213,7 +4221,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println("ActiveUnlockRunning=" + mTrustManager.isActiveUnlockRunning(mSelectedUserInteractor.getSelectedUserId())); - pw.println("userUnlockedCache[userid=" + userId + "]=" + isUserUnlocked(userId)); + pw.println("userUnlockedCache[userid=" + userId + "]=" + mUserIsUnlocked.get(userId)); pw.println("actualUserUnlocked[userid=" + userId + "]=" + mUserManager.isUserUnlocked(userId)); new DumpsysTableLogger( diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt index bebfd859f9ed..cd19aaac6831 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt @@ -116,7 +116,7 @@ constructor( fun logUpdateLockScreenUserLockedMsg( userId: Int, - userUnlocked: Boolean, + userStorageUnlocked: Boolean, encryptedOrLockdown: Boolean, ) { buffer.log( @@ -124,12 +124,12 @@ constructor( LogLevel.DEBUG, { int1 = userId - bool1 = userUnlocked + bool1 = userStorageUnlocked bool2 = encryptedOrLockdown }, { "updateLockScreenUserLockedMsg userId=$int1 " + - "userUnlocked:$bool1 encryptedOrLockdown:$bool2" + "userStorageUnlocked:$bool1 encryptedOrLockdown:$bool2" } ) } diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 12fc3c262367..b3ddde38509a 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -582,6 +582,18 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { logBuffer.log(TAG, DEBUG, { int1 = userId }, { "userUnlocked userId: $int1" }) } + fun logUserStorageUnlocked(userId: Int, result: Boolean) { + logBuffer.log( + TAG, + DEBUG, + { + int1 = userId + bool1 = result + }, + { "Invoked UserManager#isUserUnlocked $int1, result: $bool1" }, + ) + } + fun logUserStopped(userId: Int, isUnlocked: Boolean) { logBuffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java index f8b445ba069e..3cf400aa5c16 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java @@ -38,7 +38,6 @@ import android.view.IWindowManager; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.WindowManager; -import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IRemoteMagnificationAnimationCallback; @@ -138,10 +137,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks mWindowMagnifierCallback, mSysUiState, mSecureSettings, - scvhSupplier, - new SfVsyncFrameCallbackProvider(), - WindowManagerGlobal::getWindowSession, - mViewCaptureAwareWindowManager); + scvhSupplier); } } @@ -407,7 +403,8 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks } } - boolean isMagnificationSettingsPanelShowing(int displayId) { + @MainThread + private boolean isMagnificationSettingsPanelShowing(int displayId) { final MagnificationSettingsController magnificationSettingsController = mMagnificationSettingsSupplier.get(displayId); if (magnificationSettingsController != null) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 0883a0611d15..7d5cf232bcb9 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -54,11 +54,8 @@ import android.util.Range; import android.util.Size; import android.util.SparseArray; import android.util.TypedValue; -import android.view.Choreographer; import android.view.Display; import android.view.Gravity; -import android.view.IWindow; -import android.view.IWindowSession; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; @@ -80,10 +77,8 @@ import android.widget.ImageView; import androidx.annotation.UiThread; import androidx.core.math.MathUtils; -import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.accessibility.common.MagnificationConstants; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.Flags; import com.android.systemui.model.SysUiState; import com.android.systemui.res.R; @@ -127,7 +122,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private final SurfaceControl.Transaction mTransaction; private final WindowManager mWm; - private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager; private float mScale; private int mSettingsButtonIndex = MagnificationSize.DEFAULT; @@ -219,11 +213,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private int mMinWindowSize; private final WindowMagnificationAnimationController mAnimationController; - private final Supplier<IWindowSession> mGlobalWindowSessionSupplier; - private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; private final MagnificationGestureDetector mGestureDetector; private int mBounceEffectDuration; - private final Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback; private Locale mLocale; private NumberFormat mPercentFormat; private float mBounceEffectAnimationScale; @@ -258,18 +249,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold @NonNull WindowMagnifierCallback callback, SysUiState sysUiState, SecureSettings secureSettings, - Supplier<SurfaceControlViewHost> scvhSupplier, - SfVsyncFrameCallbackProvider sfVsyncFrameProvider, - Supplier<IWindowSession> globalWindowSessionSupplier, - ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) { + Supplier<SurfaceControlViewHost> scvhSupplier) { mContext = context; mHandler = handler; mAnimationController = animationController; - mAnimationController.setOnAnimationEndRunnable(() -> { - if (Flags.createWindowlessWindowMagnifier()) { - notifySourceBoundsChanged(); - } - }); + mAnimationController.setOnAnimationEndRunnable(this::notifySourceBoundsChanged); mAnimationController.setWindowMagnificationController(this); mWindowMagnifierCallback = callback; mSysUiState = sysUiState; @@ -283,7 +267,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mWm = context.getSystemService(WindowManager.class); mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds()); - mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager; mResources = mContext.getResources(); mScale = secureSettings.getFloatForUser( @@ -313,76 +296,31 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mGestureDetector = new MagnificationGestureDetector(mContext, handler, this); mWindowInsetChangeRunnable = this::onWindowInsetChanged; - mGlobalWindowSessionSupplier = globalWindowSessionSupplier; - mSfVsyncFrameProvider = sfVsyncFrameProvider; // Initialize listeners. - if (Flags.createWindowlessWindowMagnifier()) { - mMirrorViewRunnable = new Runnable() { - final Rect mPreviousBounds = new Rect(); - - @Override - public void run() { - if (mMirrorView != null) { - if (mPreviousBounds.width() != mMirrorViewBounds.width() - || mPreviousBounds.height() != mMirrorViewBounds.height()) { - mMirrorView.setSystemGestureExclusionRects(Collections.singletonList( - new Rect(0, 0, mMirrorViewBounds.width(), - mMirrorViewBounds.height()))); - mPreviousBounds.set(mMirrorViewBounds); - } - updateSystemUIStateIfNeeded(); - mWindowMagnifierCallback.onWindowMagnifierBoundsChanged( - mDisplayId, mMirrorViewBounds); - } - } - }; - - mMirrorSurfaceViewLayoutChangeListener = - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> - mMirrorView.post(this::applyTapExcludeRegion); + mMirrorViewRunnable = new Runnable() { + final Rect mPreviousBounds = new Rect(); - mMirrorViewGeometryVsyncCallback = null; - } else { - mMirrorViewRunnable = () -> { + @Override + public void run() { if (mMirrorView != null) { - final Rect oldViewBounds = new Rect(mMirrorViewBounds); - mMirrorView.getBoundsOnScreen(mMirrorViewBounds); - if (oldViewBounds.width() != mMirrorViewBounds.width() - || oldViewBounds.height() != mMirrorViewBounds.height()) { + if (mPreviousBounds.width() != mMirrorViewBounds.width() + || mPreviousBounds.height() != mMirrorViewBounds.height()) { mMirrorView.setSystemGestureExclusionRects(Collections.singletonList( - new Rect(0, 0, - mMirrorViewBounds.width(), mMirrorViewBounds.height()))); + new Rect(0, 0, mMirrorViewBounds.width(), + mMirrorViewBounds.height()))); + mPreviousBounds.set(mMirrorViewBounds); } updateSystemUIStateIfNeeded(); mWindowMagnifierCallback.onWindowMagnifierBoundsChanged( mDisplayId, mMirrorViewBounds); } - }; - - mMirrorSurfaceViewLayoutChangeListener = - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> - mMirrorView.post(this::applyTapExcludeRegion); - - mMirrorViewGeometryVsyncCallback = - l -> { - if (isActivated() && mMirrorSurface != null && calculateSourceBounds( - mMagnificationFrame, mScale)) { - // The final destination for the magnification surface should be at 0,0 - // since the ViewRootImpl's position will change - mTmpRect.set(0, 0, mMagnificationFrame.width(), - mMagnificationFrame.height()); - mTransaction.setGeometry(mMirrorSurface, mSourceBounds, mTmpRect, - Surface.ROTATION_0).apply(); - - // Notify source bounds change when the magnifier is not animating. - if (!mAnimationController.isAnimating()) { - mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, - mSourceBounds); - } - } - }; - } + } + }; + + mMirrorSurfaceViewLayoutChangeListener = + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> + mMirrorView.post(this::applyTouchableRegion); mMirrorViewLayoutChangeListener = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { @@ -463,7 +401,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (isActivated()) { updateDimensions(); - applyTapExcludeRegion(); + applyTouchableRegion(); } if (!enable) { @@ -513,9 +451,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (mMirrorView != null) { mHandler.removeCallbacks(mMirrorViewRunnable); mMirrorView.removeOnLayoutChangeListener(mMirrorViewLayoutChangeListener); - if (!Flags.createWindowlessWindowMagnifier()) { - mViewCaptureAwareWindowManager.removeView(mMirrorView); - } mMirrorView = null; } @@ -624,11 +559,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (!isActivated()) return; LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams(); params.accessibilityTitle = getAccessibilityWindowTitle(); - if (Flags.createWindowlessWindowMagnifier()) { - mSurfaceControlViewHost.relayout(params); - } else { - mWm.updateViewLayout(mMirrorView, params); - } + mSurfaceControlViewHost.relayout(params); } /** @@ -678,62 +609,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold return (oldRotation - newRotation + 4) % 4 * 90; } - private void createMirrorWindow() { - if (Flags.createWindowlessWindowMagnifier()) { - createWindowlessMirrorWindow(); - return; - } - - // The window should be the size the mirrored surface will be but also add room for the - // border and the drag handle. - int windowWidth = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin; - int windowHeight = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin; - - LayoutParams params = new LayoutParams( - windowWidth, windowHeight, - LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, - LayoutParams.FLAG_NOT_TOUCH_MODAL - | LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSPARENT); - params.gravity = Gravity.TOP | Gravity.LEFT; - params.x = mMagnificationFrame.left - mMirrorSurfaceMargin; - params.y = mMagnificationFrame.top - mMirrorSurfaceMargin; - params.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - params.receiveInsetsIgnoringZOrder = true; - params.setTitle(mContext.getString(R.string.magnification_window_title)); - params.accessibilityTitle = getAccessibilityWindowTitle(); - - mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null); - mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view); - - mMirrorBorderView = mMirrorView.findViewById(R.id.magnification_inner_border); - - // Allow taps to go through to the mirror SurfaceView below. - mMirrorSurfaceView.addOnLayoutChangeListener(mMirrorSurfaceViewLayoutChangeListener); - - mMirrorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - mMirrorView.addOnLayoutChangeListener(mMirrorViewLayoutChangeListener); - mMirrorView.setAccessibilityDelegate(new MirrorWindowA11yDelegate()); - mMirrorView.setOnApplyWindowInsetsListener((v, insets) -> { - if (!mHandler.hasCallbacks(mWindowInsetChangeRunnable)) { - mHandler.post(mWindowInsetChangeRunnable); - } - return v.onApplyWindowInsets(insets); - }); - - mViewCaptureAwareWindowManager.addView(mMirrorView, params); - - SurfaceHolder holder = mMirrorSurfaceView.getHolder(); - holder.addCallback(this); - holder.setFormat(PixelFormat.RGBA_8888); - addDragTouchListeners(); - } - private void createWindowlessMirrorWindow() { // The window should be the size the mirrored surface will be but also add room for the // border and the drag handle. @@ -802,62 +677,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } } - private void applyTapExcludeRegion() { - if (Flags.createWindowlessWindowMagnifier()) { - applyTouchableRegion(); - return; - } - - // Sometimes this can get posted and run after deleteWindowMagnification() is called. - if (mMirrorView == null) return; - - final Region tapExcludeRegion = calculateTapExclude(); - final IWindow window = IWindow.Stub.asInterface(mMirrorView.getWindowToken()); - try { - IWindowSession session = mGlobalWindowSessionSupplier.get(); - session.updateTapExcludeRegion(window, tapExcludeRegion); - } catch (RemoteException e) { - } - } - - private Region calculateTapExclude() { - Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize, - mMirrorView.getWidth() - mBorderDragSize, - mMirrorView.getHeight() - mBorderDragSize); - - Region tapExcludeRegion = new Region(); - - Rect dragArea = new Rect(); - mDragView.getHitRect(dragArea); - - Rect topLeftArea = new Rect(); - mTopLeftCornerView.getHitRect(topLeftArea); - - Rect topRightArea = new Rect(); - mTopRightCornerView.getHitRect(topRightArea); - - Rect bottomLeftArea = new Rect(); - mBottomLeftCornerView.getHitRect(bottomLeftArea); - - Rect bottomRightArea = new Rect(); - mBottomRightCornerView.getHitRect(bottomRightArea); - - Rect closeArea = new Rect(); - mCloseView.getHitRect(closeArea); - - // add tapExcludeRegion for Drag or close - tapExcludeRegion.op(dragArea, Region.Op.UNION); - tapExcludeRegion.op(topLeftArea, Region.Op.UNION); - tapExcludeRegion.op(topRightArea, Region.Op.UNION); - tapExcludeRegion.op(bottomLeftArea, Region.Op.UNION); - tapExcludeRegion.op(bottomRightArea, Region.Op.UNION); - tapExcludeRegion.op(closeArea, Region.Op.UNION); - - regionInsideDragBorder.op(tapExcludeRegion, Region.Op.DIFFERENCE); - - return regionInsideDragBorder; - } - private void applyTouchableRegion() { // Sometimes this can get posted and run after deleteWindowMagnification() is called. if (mMirrorView == null) return; @@ -1085,13 +904,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold * {@link #mMagnificationFrame}. */ private void modifyWindowMagnification(boolean computeWindowSize) { - if (Flags.createWindowlessWindowMagnifier()) { - updateMirrorSurfaceGeometry(); - updateWindowlessMirrorViewLayout(computeWindowSize); - } else { - mSfVsyncFrameProvider.postFrameCallback(mMirrorViewGeometryVsyncCallback); - updateMirrorViewLayout(computeWindowSize); - } + updateMirrorSurfaceGeometry(); + updateWindowlessMirrorViewLayout(computeWindowSize); } /** @@ -1169,58 +983,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mMirrorViewRunnable.run(); } - /** - * Updates the layout params of MirrorView based on the size of {@link #mMagnificationFrame} - * and translates MirrorView position when the view is moved close to the screen edges; - * - * @param computeWindowSize set to {@code true} to compute window size with - * {@link #mMagnificationFrame}. - */ - private void updateMirrorViewLayout(boolean computeWindowSize) { - if (!isActivated()) { - return; - } - final int maxMirrorViewX = mWindowBounds.width() - mMirrorView.getWidth(); - final int maxMirrorViewY = mWindowBounds.height() - mMirrorView.getHeight(); - - LayoutParams params = - (LayoutParams) mMirrorView.getLayoutParams(); - params.x = mMagnificationFrame.left - mMirrorSurfaceMargin; - params.y = mMagnificationFrame.top - mMirrorSurfaceMargin; - if (computeWindowSize) { - params.width = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin; - params.height = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin; - } - - // Translates MirrorView position to make MirrorSurfaceView that is inside MirrorView - // able to move close to the screen edges. - final float translationX; - final float translationY; - if (params.x < 0) { - translationX = Math.max(params.x, -mOuterBorderSize); - } else if (params.x > maxMirrorViewX) { - translationX = Math.min(params.x - maxMirrorViewX, mOuterBorderSize); - } else { - translationX = 0; - } - if (params.y < 0) { - translationY = Math.max(params.y, -mOuterBorderSize); - } else if (params.y > maxMirrorViewY) { - translationY = Math.min(params.y - maxMirrorViewY, mOuterBorderSize); - } else { - translationY = 0; - } - mMirrorView.setTranslationX(translationX); - mMirrorView.setTranslationY(translationY); - mWm.updateViewLayout(mMirrorView, params); - - // If they are not dragging the handle, we can move the drag handle immediately without - // disruption. But if they are dragging it, we avoid moving until the end of the drag. - if (!mIsDragging) { - mMirrorView.post(this::maybeRepositionButton); - } - } - @Override public boolean onTouch(View v, MotionEvent event) { if (v == mDragView @@ -1474,7 +1236,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold calculateMagnificationFrameBoundary(); updateMagnificationFramePosition((int) offsetX, (int) offsetY); if (!isActivated()) { - createMirrorWindow(); + createWindowlessMirrorWindow(); showControls(); applyResourcesValues(); } else { @@ -1766,7 +1528,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (newGravity != layoutParams.gravity) { layoutParams.gravity = newGravity; mDragView.setLayoutParams(layoutParams); - mDragView.post(this::applyTapExcludeRegion); + mDragView.post(this::applyTouchableRegion); } } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt new file mode 100644 index 000000000000..ddd6bc9ef16b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.clipboardoverlay + +/** Interface for listening to indication text changed from [ClipboardIndicationProvider]. */ +interface ClipboardIndicationCallback { + + /** Notifies the indication text changed. */ + fun onIndicationTextChanged(text: CharSequence) +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt new file mode 100644 index 000000000000..be3272369d46 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.clipboardoverlay + +/** Interface to provide the clipboard indication to be shown under the overlay. */ +interface ClipboardIndicationProvider { + + /** + * Gets the indication text async. + * + * @param callback callback for getting the indication text. + */ + fun getIndicationText(callback: ClipboardIndicationCallback) +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt new file mode 100644 index 000000000000..da94d5b518b7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.clipboardoverlay + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +@SysUISingleton +open class ClipboardIndicationProviderImpl @Inject constructor() : ClipboardIndicationProvider { + + override fun getIndicationText(callback: ClipboardIndicationCallback) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 65c01ed9eecd..ac747845267c 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -21,6 +21,7 @@ import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS; import static com.android.systemui.Flags.clipboardImageTimeout; import static com.android.systemui.Flags.clipboardSharedTransitions; +import static com.android.systemui.Flags.showClipboardIndication; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER; @@ -99,6 +100,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv private final ClipboardTransitionExecutor mTransitionExecutor; private final ClipboardOverlayView mView; + private final ClipboardIndicationProvider mClipboardIndicationProvider; private Runnable mOnSessionCompleteListener; private Runnable mOnRemoteCopyTapped; @@ -173,6 +175,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv } }; + private ClipboardIndicationCallback mIndicationCallback = new ClipboardIndicationCallback() { + @Override + public void onIndicationTextChanged(@NonNull CharSequence text) { + mView.setIndicationText(text); + } + }; + @Inject public ClipboardOverlayController(@OverlayWindowContext Context context, ClipboardOverlayView clipboardOverlayView, @@ -185,11 +194,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv @Background Executor bgExecutor, ClipboardImageLoader clipboardImageLoader, ClipboardTransitionExecutor transitionExecutor, + ClipboardIndicationProvider clipboardIndicationProvider, UiEventLogger uiEventLogger) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mClipboardImageLoader = clipboardImageLoader; mTransitionExecutor = transitionExecutor; + mClipboardIndicationProvider = clipboardIndicationProvider; mClipboardLogger = new ClipboardLogger(uiEventLogger); @@ -288,6 +299,9 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting; mClipboardModel = model; mClipboardLogger.setClipSource(mClipboardModel.getSource()); + if (showClipboardIndication()) { + mClipboardIndicationProvider.getIndicationText(mIndicationCallback); + } if (clipboardImageTimeout()) { if (shouldAnimate) { reset(); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java index 1762d82b3237..7e4d76280909 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java @@ -18,6 +18,8 @@ package com.android.systemui.clipboardoverlay; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static com.android.systemui.Flags.showClipboardIndication; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -53,6 +55,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.view.ViewCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; @@ -103,6 +106,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { private View mShareChip; private View mRemoteCopyChip; private View mActionContainerBackground; + private View mIndicationContainer; + private TextView mIndicationText; private View mDismissButton; private LinearLayout mActionContainer; private ClipboardOverlayCallbacks mClipboardCallbacks; @@ -136,6 +141,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { mShareChip = requireViewById(R.id.share_chip); mRemoteCopyChip = requireViewById(R.id.remote_copy_chip); mDismissButton = requireViewById(R.id.dismiss_button); + mIndicationContainer = requireViewById(R.id.indication_container); + mIndicationText = mIndicationContainer.findViewById(R.id.indication_text); bindDefaultActionChips(); @@ -208,6 +215,14 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { } } + void setIndicationText(CharSequence text) { + mIndicationText.setText(text); + + // Set the visibility of clipboard indication based on the text is empty or not. + int visibility = text.isEmpty() ? View.GONE : View.VISIBLE; + mIndicationContainer.setVisibility(visibility); + } + void setMinimized(boolean minimized) { if (minimized) { mMinimizedPreview.setVisibility(View.VISIBLE); @@ -221,6 +236,18 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { mPreviewBorder.setVisibility(View.VISIBLE); mActionContainer.setVisibility(View.VISIBLE); } + + if (showClipboardIndication()) { + // Adjust the margin of clipboard indication based on the minimized state. + int marginStart = minimized ? getResources().getDimensionPixelSize( + R.dimen.overlay_action_container_margin_horizontal) + : getResources().getDimensionPixelSize( + R.dimen.overlay_action_container_minimum_edge_spacing); + ConstraintLayout.LayoutParams params = + (ConstraintLayout.LayoutParams) mIndicationContainer.getLayoutParams(); + params.setMarginStart(marginStart); + mIndicationContainer.setLayoutParams(params); + } } void setInsets(WindowInsets insets, int orientation) { @@ -313,6 +340,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { setTranslationX(0); setAlpha(0); mActionContainerBackground.setVisibility(View.GONE); + mIndicationContainer.setVisibility(View.GONE); mDismissButton.setVisibility(View.GONE); mShareChip.setVisibility(View.GONE); mRemoteCopyChip.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt index 527819c73e2f..c81f0d98f3ad 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt @@ -15,18 +15,26 @@ */ package com.android.systemui.clipboardoverlay.dagger +import com.android.systemui.clipboardoverlay.ClipboardIndicationProvider +import com.android.systemui.clipboardoverlay.ClipboardIndicationProviderImpl import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionController import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionControllerImpl import dagger.Binds import dagger.Module -/** Dagger Module for code in the clipboard overlay package. */ +/** Dagger Module to provide default implementations which could be overridden. */ @Module -interface ClipboardOverlaySuppressionModule { +interface ClipboardOverlayOverrideModule { /** Provides implementation for [ClipboardOverlaySuppressionController]. */ @Binds fun provideClipboardOverlaySuppressionController( clipboardOverlaySuppressionControllerImpl: ClipboardOverlaySuppressionControllerImpl ): ClipboardOverlaySuppressionController + + /** Provides implementation for [ClipboardIndicationProvider]. */ + @Binds + fun provideClipboardIndicationProvider( + clipboardIndicationProviderImpl: ClipboardIndicationProviderImpl + ): ClipboardIndicationProvider } diff --git a/packages/SystemUI/src/com/android/systemui/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/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index 609b7330b600..2e323d40edcd 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -29,7 +29,7 @@ import com.android.systemui.accessibility.AccessibilityModule; import com.android.systemui.accessibility.SystemActionsModule; import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule; import com.android.systemui.battery.BatterySaverModule; -import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlaySuppressionModule; +import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayOverrideModule; import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; @@ -125,7 +125,7 @@ import javax.inject.Named; AospPolicyModule.class, BatterySaverModule.class, CentralSurfacesModule.class, - ClipboardOverlaySuppressionModule.class, + ClipboardOverlayOverrideModule.class, CollapsedStatusBarFragmentStartableModule.class, ConnectingDisplayViewModel.StartableModule.class, DefaultBlueprintModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java index 303f91688575..911331b8bff1 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java @@ -42,7 +42,7 @@ import androidx.annotation.Nullable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.GlobalSettings; import java.io.PrintWriter; @@ -51,6 +51,7 @@ import java.util.Map; import java.util.Objects; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; import java.util.function.Consumer; import javax.inject.Inject; @@ -74,7 +75,6 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { static final String TAG = "SysUIFlags"; private final FlagManager mFlagManager; - private final Context mContext; private final GlobalSettings mGlobalSettings; private final Resources mResources; private final SystemPropertiesHelper mSystemProperties; @@ -84,6 +84,8 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { private final Map<String, String> mStringFlagCache = new ConcurrentHashMap<>(); private final Map<String, Integer> mIntFlagCache = new ConcurrentHashMap<>(); private final Restarter mRestarter; + private final UserTracker mUserTracker; + private final Executor mMainExecutor; private final ServerFlagReader.ChangeListener mOnPropertiesChanged = new ServerFlagReader.ChangeListener() { @@ -118,6 +120,18 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { } }; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + mContext.unregisterReceiver(mReceiver); + mContext = userContext; + registerReceiver(); + } + }; + + private Context mContext; + @Inject public FeatureFlagsClassicDebug( FlagManager flagManager, @@ -128,10 +142,11 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { ServerFlagReader serverFlagReader, @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags, Restarter restarter, - UserContextProvider userContextProvider) { + UserTracker userTracker, + @Main Executor executor) { mFlagManager = flagManager; if (classicFlagsMultiUser()) { - mContext = userContextProvider.createCurrentUserContext(context); + mContext = userTracker.createCurrentUserContext(context); } else { mContext = context; } @@ -141,19 +156,28 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { mServerFlagReader = serverFlagReader; mAllFlags = allFlags; mRestarter = restarter; + mUserTracker = userTracker; + mMainExecutor = executor; } /** Call after construction to setup listeners. */ void init() { - IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_SET_FLAG); - filter.addAction(ACTION_GET_FLAGS); mFlagManager.setOnSettingsChangedAction( suppressRestart -> restartSystemUI(suppressRestart, "Settings changed")); mFlagManager.setClearCacheAction(this::removeFromCache); + registerReceiver(); + if (classicFlagsMultiUser()) { + mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); + } + mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged); + } + + void registerReceiver() { + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_SET_FLAG); + filter.addAction(ACTION_GET_FLAGS); mContext.registerReceiver(mReceiver, filter, null, null, Context.RECEIVER_EXPORTED_UNAUDITED); - mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 6ac0a3f8443f..021cce6d1e23 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -20,6 +20,7 @@ import android.animation.ValueAnimator import android.annotation.SuppressLint import android.app.DreamManager import com.android.app.animation.Interpolators +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor @@ -41,7 +42,6 @@ import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.debounce -import com.android.app.tracing.coroutines.launchTraced as launch @SysUISingleton class FromDozingTransitionInteractor @@ -135,11 +135,22 @@ constructor( if (!deviceEntryInteractor.isLockscreenEnabled()) { if (!SceneContainerFlag.isEnabled) { - startTransitionTo(KeyguardState.GONE) + startTransitionTo( + KeyguardState.GONE, + ownerReason = "lockscreen not enabled", + ) } } else if (canDismissLockscreen() || isKeyguardGoingAway) { if (!SceneContainerFlag.isEnabled) { - startTransitionTo(KeyguardState.GONE) + startTransitionTo( + KeyguardState.GONE, + ownerReason = + if (canDismissLockscreen()) { + "canDismissLockscreen()" + } else { + "isKeyguardGoingAway" + }, + ) } } else if (primaryBouncerShowing) { if (!SceneContainerFlag.isEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index 0dae17c594c8..cd62d5f3b6e1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -16,9 +16,11 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.logging.KeyguardLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.log.core.LogLevel.VERBOSE @@ -29,7 +31,6 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNoti import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.debounce -import com.android.app.tracing.coroutines.launchTraced as launch private val TAG = KeyguardTransitionAuditLogger::class.simpleName!! @@ -48,6 +49,7 @@ constructor( private val aodBurnInViewModel: AodBurnInViewModel, private val shadeInteractor: ShadeInteractor, private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, ) { fun start() { @@ -84,6 +86,18 @@ constructor( } scope.launch { + deviceEntryInteractor.isUnlocked.collect { + logger.log(TAG, VERBOSE, "DeviceEntry isUnlocked", it) + } + } + + scope.launch { + deviceEntryInteractor.isLockscreenEnabled.collect { + logger.log(TAG, VERBOSE, "DeviceEntry isLockscreenEnabled", it) + } + } + + scope.launch { keyguardInteractor.primaryBouncerShowing.collect { logger.log(TAG, VERBOSE, "Primary bouncer showing", it) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index abd7f90bbf22..7d4d377c768a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import android.util.Log +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -33,7 +34,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch /** * Each TransitionInteractor is responsible for determining under which conditions to notify @@ -201,9 +201,18 @@ sealed class TransitionInteractor( scope.launch { keyguardInteractor.onCameraLaunchDetected.filterRelevantKeyguardState().collect { if (!maybeHandleInsecurePowerGesture()) { + val lastStep = transitionInteractor.transitionState.value + val modeOnCanceled = + if (lastStep.to == KeyguardState.AOD) { + // Enabled smooth transition when double-tap camera cancels + // transition to AOD + TransitionModeOnCanceled.REVERSE + } else { + TransitionModeOnCanceled.RESET + } startTransitionTo( toState = KeyguardState.OCCLUDED, - modeOnCanceled = TransitionModeOnCanceled.RESET, + modeOnCanceled = modeOnCanceled, ownerReason = "keyguardInteractor.onCameraLaunchDetected", ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index 2c9884a63990..f473a82138e6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -126,14 +126,7 @@ constructor( sceneInteractor.get().transitionState.flatMapLatestConflated { state -> when { state.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Gone) -> - (state as Transition).isUserInputOngoing.flatMapLatestConflated { - isUserInputOngoing -> - if (isUserInputOngoing) { - isDeviceEntered - } else { - flowOf(true) - } - } + isDeviceEntered state.isTransitioning(from = Scenes.Bouncer, to = Scenes.Gone) -> (state as Transition).progress.map { progress -> progress > diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt index c78e0c9f5266..478372d4dc0c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt @@ -30,6 +30,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.model.ClockSize +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.res.R @@ -42,8 +43,10 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @@ -164,9 +167,17 @@ constructor( private fun burnIn(params: BurnInParameters): Flow<BurnInModel> { return combine( - keyguardTransitionInteractor.transitionValue(KeyguardState.AOD).map { - Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it) - }, + merge( + keyguardTransitionInteractor.transition(Edge.create(to = KeyguardState.AOD)), + keyguardTransitionInteractor + .transition(Edge.create(from = KeyguardState.AOD)) + .map { it.copy(value = 1f - it.value) }, + keyguardTransitionInteractor + .transition(Edge.create(to = KeyguardState.LOCKSCREEN)) + .filter { it.from != KeyguardState.AOD } + .map { it.copy(value = 0f) }, + ) + .map { Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value) }, burnInInteractor.burnIn( xDimenResourceId = R.dimen.burn_in_prevention_offset_x, yDimenResourceId = R.dimen.burn_in_prevention_offset_y, diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt index bf2aa7efc0c4..56885c3eea9f 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt @@ -18,7 +18,7 @@ package com.android.systemui.mediaprojection.appselector.data import android.annotation.ColorInt import android.annotation.UserIdInt -import android.app.ActivityManager.RecentTaskInfo +import android.app.TaskInfo import android.content.ComponentName import com.android.wm.shell.shared.split.SplitBounds @@ -34,7 +34,7 @@ data class RecentTask( val splitBounds: SplitBounds?, ) { constructor( - taskInfo: RecentTaskInfo, + taskInfo: TaskInfo, isForegroundTask: Boolean, userType: UserType, splitBounds: SplitBounds? = null diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt index 82e58cc7f1d9..d94424c59376 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt @@ -23,7 +23,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.settings.UserTracker import com.android.systemui.util.kotlin.getOrNull import com.android.wm.shell.recents.RecentTasks -import com.android.wm.shell.shared.GroupedRecentTaskInfo +import com.android.wm.shell.shared.GroupedTaskInfo import java.util.Optional import java.util.concurrent.Executor import javax.inject.Inject @@ -51,7 +51,7 @@ constructor( override suspend fun loadRecentTasks(): List<RecentTask> = withContext(coroutineDispatcher) { - val groupedTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList() + val groupedTasks: List<GroupedTaskInfo> = recents?.getTasks() ?: emptyList() // Note: the returned task list is from the most-recent to least-recent order. // When opening the app selector in full screen, index 0 will be just the app selector // activity and a null second task, so the foreground task will be index 1, but when @@ -86,7 +86,7 @@ constructor( } } - private suspend fun RecentTasks.getTasks(): List<GroupedRecentTaskInfo> = + private suspend fun RecentTasks.getTasks(): List<GroupedTaskInfo> = suspendCoroutine { continuation -> getRecentTasks( Integer.MAX_VALUE, diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 1fbe8e2f21e5..580a51a3dc0a 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -46,7 +46,6 @@ import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor import com.android.systemui.model.SceneContainerPlugin import com.android.systemui.model.SysUiState import com.android.systemui.model.updateFlags @@ -97,7 +96,6 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -138,7 +136,6 @@ constructor( private val uiEventLogger: UiEventLogger, private val sceneBackInteractor: SceneBackInteractor, private val shadeSessionStorage: SessionStorage, - private val windowMgrLockscreenVisInteractor: WindowManagerLockscreenVisibilityInteractor, private val keyguardEnabledInteractor: KeyguardEnabledInteractor, private val dismissCallbackRegistry: DismissCallbackRegistry, private val statusBarStateController: SysuiStatusBarStateController, @@ -270,27 +267,6 @@ constructor( handleDeviceUnlockStatus() handlePowerState() handleShadeTouchability() - handleSurfaceBehindKeyguardVisibility() - } - - private fun handleSurfaceBehindKeyguardVisibility() { - applicationScope.launch { - sceneInteractor.currentScene.collectLatest { currentScene -> - if (currentScene == Scenes.Lockscreen) { - // Wait for the screen to be on - powerInteractor.isAwake.first { it } - // Wait for surface to become visible - windowMgrLockscreenVisInteractor.surfaceBehindVisibility.first { it } - // Make sure the device is actually unlocked before force-changing the scene - deviceUnlockedInteractor.deviceUnlockStatus.first { it.isUnlocked } - // Override the current transition, if any, by forcing the scene to Gone - sceneInteractor.changeScene( - toScene = Scenes.Gone, - loggingReason = "surface behind keyguard is visible", - ) - } - } - } } private fun handleBouncerImeVisibility() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 520cbf9d80d9..8c5a711d6a75 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -619,10 +619,11 @@ public class KeyguardIndicationController { } private void updateLockScreenUserLockedMsg(int userId) { - boolean userUnlocked = mKeyguardUpdateMonitor.isUserUnlocked(userId); + boolean userStorageUnlocked = mKeyguardUpdateMonitor.isUserUnlocked(userId); boolean encryptedOrLockdown = mKeyguardUpdateMonitor.isEncryptedOrLockdown(userId); - mKeyguardLogger.logUpdateLockScreenUserLockedMsg(userId, userUnlocked, encryptedOrLockdown); - if (!userUnlocked || encryptedOrLockdown) { + mKeyguardLogger.logUpdateLockScreenUserLockedMsg(userId, userStorageUnlocked, + encryptedOrLockdown); + if (!userStorageUnlocked || encryptedOrLockdown) { mRotateTextViewController.updateIndication( INDICATION_TYPE_USER_LOCKED, new KeyguardIndication.Builder() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt index b166defb96a2..2dcb706234b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt @@ -95,7 +95,7 @@ constructor( private val smartReplyStateInflater: SmartReplyStateInflater, private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, private val headsUpStyleProvider: HeadsUpStyleProvider, - private val logger: NotificationRowContentBinderLogger + private val logger: NotificationRowContentBinderLogger, ) : NotificationRowContentBinder { init { @@ -110,7 +110,7 @@ constructor( @InflationFlag contentToBind: Int, bindParams: BindParams, forceInflate: Boolean, - callback: InflationCallback? + callback: InflationCallback?, ) { if (row.isRemoved) { // We don't want to reinflate anything for removed notifications. Otherwise views might @@ -147,7 +147,7 @@ constructor( /* isMediaFlagEnabled = */ smartReplyStateInflater, notifLayoutInflaterFactoryProvider, headsUpStyleProvider, - logger + logger, ) if (inflateSynchronously) { task.onPostExecute(task.doInBackground()) @@ -165,7 +165,7 @@ constructor( @InflationFlag reInflateFlags: Int, builder: Notification.Builder, packageContext: Context, - smartRepliesInflater: SmartReplyStateInflater + smartRepliesInflater: SmartReplyStateInflater, ): InflationProgress { val systemUIContext = row.context val result = @@ -229,7 +229,7 @@ constructor( row, remoteInputManager.remoteViewsOnClickHandler, /* callback= */ null, - logger + logger, ) return result } @@ -246,7 +246,7 @@ constructor( override fun unbindContent( entry: NotificationEntry, row: ExpandableNotificationRow, - @InflationFlag contentToUnbind: Int + @InflationFlag contentToUnbind: Int, ) { logger.logUnbinding(entry, contentToUnbind) var curFlag = 1 @@ -268,7 +268,7 @@ constructor( private fun freeNotificationView( entry: NotificationEntry, row: ExpandableNotificationRow, - @InflationFlag inflateFlag: Int + @InflationFlag inflateFlag: Int, ) { when (inflateFlag) { FLAG_CONTENT_VIEW_CONTRACTED -> @@ -319,7 +319,7 @@ constructor( */ private fun cancelContentViewFrees( row: ExpandableNotificationRow, - @InflationFlag contentViews: Int + @InflationFlag contentViews: Int, ) { if (contentViews and FLAG_CONTENT_VIEW_CONTRACTED != 0) { row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED) @@ -372,7 +372,7 @@ constructor( private val smartRepliesInflater: SmartReplyStateInflater, private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, private val headsUpStyleProvider: HeadsUpStyleProvider, - private val logger: NotificationRowContentBinderLogger + private val logger: NotificationRowContentBinderLogger, ) : AsyncTask<Void, Void, Result<InflationProgress>>(), InflationCallback, InflationTask { private val context: Context get() = row.context @@ -393,7 +393,7 @@ constructor( context.packageManager.getApplicationInfoAsUser( packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, - userId + userId, ) } catch (e: PackageManager.NameNotFoundException) { return @@ -442,11 +442,11 @@ constructor( notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider, headsUpStyleProvider = headsUpStyleProvider, conversationProcessor = conversationProcessor, - logger = logger + logger = logger, ) logger.logAsyncTaskProgress( entry, - "getting existing smart reply state (on wrong thread!)" + "getting existing smart reply state (on wrong thread!)", ) val previousSmartReplyState: InflatedSmartReplyState? = row.existingSmartReplyState logger.logAsyncTaskProgress(entry, "inflating smart reply views") @@ -469,7 +469,7 @@ constructor( reInflateFlags, entry, context, - logger + logger, ) } } @@ -483,7 +483,7 @@ constructor( reInflateFlags, entry, context, - logger + logger, ) } } @@ -513,7 +513,7 @@ constructor( row, remoteViewClickHandler, this /* callback */, - logger + logger, ) } .onFailure { error -> handleError(error as Exception) } @@ -530,7 +530,7 @@ constructor( Log.e(TAG, "couldn't inflate view for notification $ident", e) callback?.handleInflationException( row.entry, - InflationException("Couldn't inflate contentViews$e") + InflationException("Couldn't inflate contentViews$e"), ) // Cancel any image loading tasks, not useful any more @@ -618,7 +618,7 @@ constructor( packageContext: Context, previousSmartReplyState: InflatedSmartReplyState?, inflater: SmartReplyStateInflater, - logger: NotificationRowContentBinderLogger + logger: NotificationRowContentBinderLogger, ) { val inflateContracted = (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0 && @@ -641,7 +641,7 @@ constructor( packageContext, entry, previousSmartReplyState, - result.inflatedSmartReplyState!! + result.inflatedSmartReplyState!!, ) } if (inflateHeadsUp) { @@ -652,7 +652,7 @@ constructor( packageContext, entry, previousSmartReplyState, - result.inflatedSmartReplyState!! + result.inflatedSmartReplyState!!, ) } } @@ -670,7 +670,7 @@ constructor( notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, headsUpStyleProvider: HeadsUpStyleProvider, conversationProcessor: ConversationNotificationProcessor, - logger: NotificationRowContentBinderLogger + logger: NotificationRowContentBinderLogger, ): InflationProgress { // process conversations and extract the messaging style val messagingStyle = @@ -713,7 +713,7 @@ constructor( logger.logAsyncTaskProgress(entry, "inflating public single line view model") SingleLineViewInflater.inflateRedactedSingleLineViewModel( systemUIContext, - entry.ranking.isConversation + entry.ranking.isConversation, ) } else null @@ -746,7 +746,7 @@ constructor( row: ExpandableNotificationRow, notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, headsUpStyleProvider: HeadsUpStyleProvider, - logger: NotificationRowContentBinderLogger + logger: NotificationRowContentBinderLogger, ): NewRemoteViews { return TraceUtils.trace("NotificationContentInflater.createRemoteViews") { val entryForLogging: NotificationEntry = row.entry @@ -754,7 +754,7 @@ constructor( if (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0) { logger.logAsyncTaskProgress( entryForLogging, - "creating contracted remote view" + "creating contracted remote view", ) createContentView(builder, isMinimized, usesIncreasedHeight) } else null @@ -762,7 +762,7 @@ constructor( if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) { logger.logAsyncTaskProgress( entryForLogging, - "creating expanded remote view" + "creating expanded remote view", ) createExpandedView(builder, isMinimized) } else null @@ -770,7 +770,7 @@ constructor( if (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0) { logger.logAsyncTaskProgress( entryForLogging, - "creating heads up remote view" + "creating heads up remote view", ) val isHeadsUpCompact = headsUpStyleProvider.shouldApplyCompactStyle() if (isHeadsUpCompact) { @@ -791,7 +791,7 @@ constructor( ) { logger.logAsyncTaskProgress( entryForLogging, - "creating group summary remote view" + "creating group summary remote view", ) builder.makeNotificationGroupHeader() } else null @@ -802,7 +802,7 @@ constructor( ) { logger.logAsyncTaskProgress( entryForLogging, - "creating low-priority group summary remote view" + "creating low-priority group summary remote view", ) builder.makeLowPriorityContentView(true /* useRegularSubtext */) } else null @@ -812,7 +812,7 @@ constructor( expanded = expanded, public = public, normalGroupHeader = normalGroupHeader, - minimizedGroupHeader = minimizedGroupHeader + minimizedGroupHeader = minimizedGroupHeader, ) .withLayoutInflaterFactory(row, notifLayoutInflaterFactoryProvider) } @@ -820,7 +820,7 @@ constructor( private fun NewRemoteViews.withLayoutInflaterFactory( row: ExpandableNotificationRow, - provider: NotifLayoutInflaterFactory.Provider + provider: NotifLayoutInflaterFactory.Provider, ): NewRemoteViews { contracted?.let { it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_CONTRACTED) @@ -848,7 +848,7 @@ constructor( row: ExpandableNotificationRow, remoteViewClickHandler: InteractionHandler?, callback: InflationCallback?, - logger: NotificationRowContentBinderLogger + logger: NotificationRowContentBinderLogger, ): CancellationSignal { Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row)) val privateLayout = row.privateLayout @@ -859,7 +859,7 @@ constructor( val isNewView = !canReapplyRemoteView( newView = result.remoteViews.contracted, - oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED) + oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED), ) val applyCallback: ApplyCallback = object : ApplyCallback() { @@ -890,7 +890,7 @@ constructor( existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED), runningInflations = runningInflations, applyCallback = applyCallback, - logger = logger + logger = logger, ) } flag = FLAG_CONTENT_VIEW_EXPANDED @@ -898,7 +898,7 @@ constructor( val isNewView = !canReapplyRemoteView( newView = result.remoteViews.expanded, - oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED) + oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED), ) val applyCallback: ApplyCallback = object : ApplyCallback() { @@ -929,7 +929,7 @@ constructor( existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_EXPANDED), runningInflations = runningInflations, applyCallback = applyCallback, - logger = logger + logger = logger, ) } flag = FLAG_CONTENT_VIEW_HEADS_UP @@ -937,7 +937,7 @@ constructor( val isNewView = !canReapplyRemoteView( newView = result.remoteViews.headsUp, - oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP) + oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP), ) val applyCallback: ApplyCallback = object : ApplyCallback() { @@ -968,7 +968,7 @@ constructor( existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_HEADSUP), runningInflations = runningInflations, applyCallback = applyCallback, - logger = logger + logger = logger, ) } flag = FLAG_CONTENT_VIEW_PUBLIC @@ -976,7 +976,7 @@ constructor( val isNewView = !canReapplyRemoteView( newView = result.remoteViews.public, - oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC) + oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC), ) val applyCallback: ApplyCallback = object : ApplyCallback() { @@ -1007,7 +1007,7 @@ constructor( existingWrapper = publicLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED), runningInflations = runningInflations, applyCallback = applyCallback, - logger = logger + logger = logger, ) } if (AsyncGroupHeaderViewInflation.isEnabled) { @@ -1018,7 +1018,7 @@ constructor( !canReapplyRemoteView( newView = result.remoteViews.normalGroupHeader, oldView = - remoteViewCache.getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER) + remoteViewCache.getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER), ) val applyCallback: ApplyCallback = object : ApplyCallback() { @@ -1049,7 +1049,7 @@ constructor( existingWrapper = childrenContainer.notificationHeaderWrapper, runningInflations = runningInflations, applyCallback = applyCallback, - logger = logger + logger = logger, ) } if (reInflateFlags and FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER != 0) { @@ -1059,15 +1059,15 @@ constructor( oldView = remoteViewCache.getCachedView( entry, - FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER - ) + FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER, + ), ) val applyCallback: ApplyCallback = object : ApplyCallback() { override fun setResultView(v: View) { logger.logAsyncTaskProgress( entry, - "low-priority group header view applied" + "low-priority group header view applied", ) result.inflatedMinimizedGroupHeaderView = v as NotificationHeaderView? @@ -1095,7 +1095,7 @@ constructor( existingWrapper = childrenContainer.minimizedGroupHeaderWrapper, runningInflations = runningInflations, applyCallback = applyCallback, - logger = logger + logger = logger, ) } } @@ -1110,7 +1110,7 @@ constructor( callback, entry, row, - logger + logger, ) val cancellationSignal = CancellationSignal() cancellationSignal.setOnCancelListener { @@ -1142,7 +1142,7 @@ constructor( existingWrapper: NotificationViewWrapper?, runningInflations: HashMap<Int, CancellationSignal>, applyCallback: ApplyCallback, - logger: NotificationRowContentBinderLogger + logger: NotificationRowContentBinderLogger, ) { val newContentView: RemoteViews = applyCallback.remoteView if (inflateSynchronously) { @@ -1152,7 +1152,7 @@ constructor( newContentView.apply( result.packageContext, parentLayout, - remoteViewClickHandler + remoteViewClickHandler, ) validateView(v, entry, row.resources) applyCallback.setResultView(v) @@ -1162,7 +1162,7 @@ constructor( newContentView.reapply( result.packageContext, existingView, - remoteViewClickHandler + remoteViewClickHandler, ) validateView(existingView, entry, row.resources) existingWrapper.onReinflated() @@ -1174,7 +1174,7 @@ constructor( row.entry, callback, logger, - "applying view synchronously" + "applying view synchronously", ) // Add a running inflation to make sure we don't trigger callbacks. // Safe to do because only happens in tests. @@ -1199,7 +1199,7 @@ constructor( row.entry, callback, logger, - "applied invalid view" + "applied invalid view", ) runningInflations.remove(inflationId) return @@ -1219,7 +1219,7 @@ constructor( callback, entry, row, - logger + logger, ) } @@ -1234,20 +1234,20 @@ constructor( newContentView.apply( result.packageContext, parentLayout, - remoteViewClickHandler + remoteViewClickHandler, ) } else { newContentView.reapply( result.packageContext, existingView, - remoteViewClickHandler + remoteViewClickHandler, ) existingView!! } Log.wtf( TAG, "Async Inflation failed but normal inflation finished normally.", - e + e, ) onViewApplied(newView) } catch (anotherException: Exception) { @@ -1258,7 +1258,7 @@ constructor( row.entry, callback, logger, - "applying view" + "applying view", ) } } @@ -1270,7 +1270,7 @@ constructor( parentLayout, inflationExecutor, listener, - remoteViewClickHandler + remoteViewClickHandler, ) } else { newContentView.reapplyAsync( @@ -1278,7 +1278,7 @@ constructor( existingView, inflationExecutor, listener, - remoteViewClickHandler + remoteViewClickHandler, ) } runningInflations[inflationId] = cancellationSignal @@ -1299,7 +1299,7 @@ constructor( private fun satisfiesMinHeightRequirement( view: View, entry: NotificationEntry, - resources: Resources + resources: Resources, ): Boolean { return if (!requiresHeightCheck(entry)) { true @@ -1353,7 +1353,7 @@ constructor( notification: NotificationEntry, callback: InflationCallback?, logger: NotificationRowContentBinderLogger, - logContext: String + logContext: String, ) { Assert.isMainThread() logger.logAsyncTaskException(notification, logContext, e) @@ -1375,7 +1375,7 @@ constructor( endListener: InflationCallback?, entry: NotificationEntry, row: ExpandableNotificationRow, - logger: NotificationRowContentBinderLogger + logger: NotificationRowContentBinderLogger, ): Boolean { Assert.isMainThread() if (runningInflations.isNotEmpty()) { @@ -1439,19 +1439,19 @@ constructor( FLAG_CONTENT_VIEW_CONTRACTED, result.remoteViews.contracted, result.inflatedContentView, - privateLayout::setContractedChild + privateLayout::setContractedChild, ) remoteViewsUpdater.setContentView( FLAG_CONTENT_VIEW_EXPANDED, result.remoteViews.expanded, result.inflatedExpandedView, - privateLayout::setExpandedChild + privateLayout::setExpandedChild, ) remoteViewsUpdater.setSmartReplies( FLAG_CONTENT_VIEW_EXPANDED, result.remoteViews.expanded, result.expandedInflatedSmartReplies, - privateLayout::setExpandedInflatedSmartReplies + privateLayout::setExpandedInflatedSmartReplies, ) if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) { row.setExpandable(result.remoteViews.expanded != null) @@ -1460,19 +1460,19 @@ constructor( FLAG_CONTENT_VIEW_HEADS_UP, result.remoteViews.headsUp, result.inflatedHeadsUpView, - privateLayout::setHeadsUpChild + privateLayout::setHeadsUpChild, ) remoteViewsUpdater.setSmartReplies( FLAG_CONTENT_VIEW_HEADS_UP, result.remoteViews.headsUp, result.headsUpInflatedSmartReplies, - privateLayout::setHeadsUpInflatedSmartReplies + privateLayout::setHeadsUpInflatedSmartReplies, ) remoteViewsUpdater.setContentView( FLAG_CONTENT_VIEW_PUBLIC, result.remoteViews.public, result.inflatedPublicView, - publicLayout::setContractedChild + publicLayout::setContractedChild, ) if (AsyncGroupHeaderViewInflation.isEnabled) { remoteViewsUpdater.setContentView( @@ -1540,7 +1540,7 @@ constructor( private fun createExpandedView( builder: Notification.Builder, - isMinimized: Boolean + isMinimized: Boolean, ): RemoteViews? { @Suppress("DEPRECATION") val bigContentView: RemoteViews? = builder.createBigContentView() @@ -1558,7 +1558,7 @@ constructor( private fun createContentView( builder: Notification.Builder, isMinimized: Boolean, - useLarge: Boolean + useLarge: Boolean, ): RemoteViews { return if (isMinimized) { builder.makeLowPriorityContentView(false /* useRegularSubtext */) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt index 7177a7bd473a..08c1d71b86c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt @@ -73,8 +73,8 @@ constructor(private val userManager: UserManager, dumpManager: DumpManager) : private val cache = NotifCollectionCache<Boolean>() override fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean { - val packageContext = notification.getPackageContext(context) return cache.getOrFetch(notification.packageName) { + val packageContext = notification.getPackageContext(context) !belongsToHeadlessSystemApp(packageContext) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt index f7ad3205f3dd..9440a9364b62 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt @@ -18,6 +18,7 @@ package com.android.systemui.volume.dialog.dagger import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope +import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent import dagger.BindsInstance import dagger.Subcomponent import kotlinx.coroutines.CoroutineScope @@ -40,6 +41,8 @@ interface VolumeDialogComponent { @VolumeDialogScope fun volumeDialog(): com.android.systemui.volume.dialog.VolumeDialog + fun sliderComponentFactory(): VolumeDialogSliderComponent.Factory + @Subcomponent.Factory interface Factory { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt new file mode 100644 index 000000000000..538ee47915a8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.sliders.dagger + +import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType +import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder +import dagger.BindsInstance +import dagger.Subcomponent + +/** + * This component hosts all the stuff, that Volume Dialog sliders need. It's recreated alongside + * each slider view. + */ +@VolumeDialogSliderScope +@Subcomponent +interface VolumeDialogSliderComponent { + + fun sliderViewBinder(): VolumeDialogSliderViewBinder + + @Subcomponent.Factory + interface Factory { + + fun create(@BindsInstance sliderType: VolumeDialogSliderType): VolumeDialogSliderComponent + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt new file mode 100644 index 000000000000..9f5e0f6ba1aa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.sliders.dagger + +import javax.inject.Scope + +/** + * Volume Panel Slider dependency injection scope. This scope is created for each of the volume + * sliders in the dialog. + */ +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +@Scope +annotation class VolumeDialogSliderScope diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt index 876bf2c4a154..2967fe8ca906 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt @@ -17,22 +17,21 @@ package com.android.systemui.volume.dialog.sliders.domain.interactor import com.android.systemui.plugins.VolumeDialogController -import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel +import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.mapNotNull /** Operates a state of particular slider of the Volume Dialog. */ +@VolumeDialogSliderScope class VolumeDialogSliderInteractor -@AssistedInject +@Inject constructor( - @Assisted private val sliderType: VolumeDialogSliderType, + private val sliderType: VolumeDialogSliderType, volumeDialogStateInteractor: VolumeDialogStateInteractor, private val volumeDialogController: VolumeDialogController, ) { @@ -56,11 +55,4 @@ constructor( setActiveStream(sliderType.audioStream) } } - - @VolumeDialogScope - @AssistedFactory - interface Factory { - - fun create(sliderType: VolumeDialogSliderType): VolumeDialogSliderInteractor - } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index 3bf8c54cb9d8..1c231b521bae 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -24,16 +24,14 @@ import com.android.systemui.lifecycle.WindowLifecycleState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.lifecycle.viewModel import com.android.systemui.res.R -import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel +import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory import com.android.systemui.volume.dialog.ui.utils.awaitAnimation import com.google.android.material.slider.LabelFormatter import com.google.android.material.slider.Slider -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import javax.inject.Inject import kotlin.math.roundToInt import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.launchIn @@ -41,10 +39,11 @@ import kotlinx.coroutines.flow.onEach private const val PROGRESS_CHANGE_ANIMATION_DURATION_MS = 80L +@VolumeDialogSliderScope class VolumeDialogSliderViewBinder -@AssistedInject +@Inject constructor( - @Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel, + private val viewModelFactory: VolumeDialogSliderViewModel.Factory, private val jankListenerFactory: JankListenerFactory, ) { @@ -58,7 +57,7 @@ constructor( viewModel( traceName = "VolumeDialogSliderViewBinder", minWindowLifecycleState = WindowLifecycleState.ATTACHED, - factory = { viewModelProvider() }, + factory = { viewModelFactory.create() }, ) { viewModel -> sliderView.addOnChangeListener { _, value, fromUser -> viewModel.setStreamVolume(value.roundToInt(), fromUser) @@ -85,15 +84,6 @@ constructor( ) } } - - @AssistedFactory - @VolumeDialogScope - interface Factory { - - fun create( - viewModelProvider: () -> VolumeDialogSliderViewModel - ): VolumeDialogSliderViewBinder - } } private suspend fun Slider.setValueAnimated( diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt index 0a4e3f481e88..a17c1e541b5e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt @@ -28,7 +28,6 @@ import com.android.systemui.res.R import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel import javax.inject.Inject -import kotlin.math.abs import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -50,15 +49,17 @@ constructor(private val viewModelFactory: VolumeDialogSlidersViewModel.Factory) ) { viewModel -> viewModel.sliders .onEach { uiModel -> - uiModel.sliderViewBinder.bind(volumeDialog) + uiModel.sliderComponent.sliderViewBinder().bind(volumeDialog) - val floatingSliderViewBinders = uiModel.floatingSliderViewBinders + val floatingSliderViewBinders = uiModel.floatingSliderComponent floatingSlidersContainer.ensureChildCount( viewLayoutId = R.layout.volume_dialog_slider_floating, count = floatingSliderViewBinders.size, ) - floatingSliderViewBinders.fastForEachIndexed { index, viewBinder -> - viewBinder.bind(floatingSlidersContainer.getChildAt(index)) + floatingSliderViewBinders.fastForEachIndexed { index, sliderComponent -> + sliderComponent + .sliderViewBinder() + .bind(floatingSlidersContainer.getChildAt(index)) } } .launchIn(this) @@ -76,7 +77,7 @@ private fun ViewGroup.ensureChildCount(@LayoutRes viewLayoutId: Int, count: Int) } childCountDelta < 0 -> { val inflater = LayoutInflater.from(context) - repeat(abs(childCountDelta)) { inflater.inflate(viewLayoutId, this, true) } + repeat(-childCountDelta) { inflater.inflate(viewLayoutId, this, true) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt index ea0b49d5294e..cf04d45d54ff 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt @@ -22,7 +22,6 @@ import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor -import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope @@ -53,7 +52,7 @@ private const val VOLUME_UPDATE_GRACE_PERIOD = 1000 class VolumeDialogSliderViewModel @AssistedInject constructor( - @Assisted private val interactor: VolumeDialogSliderInteractor, + private val interactor: VolumeDialogSliderInteractor, private val visibilityInteractor: VolumeDialogVisibilityInteractor, @VolumeDialog private val coroutineScope: CoroutineScope, private val systemClock: SystemClock, @@ -90,11 +89,11 @@ constructor( private fun getTimestampMillis(): Long = systemClock.uptimeMillis() + private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long) + @AssistedFactory interface Factory { - fun create(interactor: VolumeDialogSliderInteractor): VolumeDialogSliderViewModel + fun create(): VolumeDialogSliderViewModel } - - private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt index b5b292fa4a66..d1972231d373 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt @@ -17,16 +17,13 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog -import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor +import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor -import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType -import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -36,29 +33,21 @@ class VolumeDialogSlidersViewModel constructor( @VolumeDialog coroutineScope: CoroutineScope, private val slidersInteractor: VolumeDialogSlidersInteractor, - private val sliderInteractorFactory: VolumeDialogSliderInteractor.Factory, - private val sliderViewModelFactory: VolumeDialogSliderViewModel.Factory, - private val sliderViewBinderFactory: VolumeDialogSliderViewBinder.Factory, + private val sliderComponentFactory: VolumeDialogSliderComponent.Factory, ) { val sliders: Flow<VolumeDialogSliderUiModel> = slidersInteractor.sliders - .distinctUntilChanged() .map { slidersModel -> VolumeDialogSliderUiModel( - sliderViewBinder = createSliderViewBinder(slidersModel.slider), - floatingSliderViewBinders = - slidersModel.floatingSliders.map(::createSliderViewBinder), + sliderComponent = sliderComponentFactory.create(slidersModel.slider), + floatingSliderComponent = + slidersModel.floatingSliders.map(sliderComponentFactory::create), ) } .stateIn(coroutineScope, SharingStarted.Eagerly, null) .filterNotNull() - private fun createSliderViewBinder(type: VolumeDialogSliderType): VolumeDialogSliderViewBinder = - sliderViewBinderFactory.create { - sliderViewModelFactory.create(sliderInteractorFactory.create(type)) - } - @AssistedFactory interface Factory { @@ -68,6 +57,6 @@ constructor( /** Models slider ui */ data class VolumeDialogSliderUiModel( - val sliderViewBinder: VolumeDialogSliderViewBinder, - val floatingSliderViewBinders: List<VolumeDialogSliderViewBinder>, + val sliderComponent: VolumeDialogSliderComponent, + val floatingSliderComponent: List<VolumeDialogSliderComponent>, ) diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt index 9be669f3df0c..869a6a2e87d5 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt @@ -18,16 +18,19 @@ package com.android.systemui.volume.dialog.ui.viewmodel import android.content.Context import com.android.systemui.res.R +import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor +import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel import com.android.systemui.volume.dialog.shared.model.streamLabel -import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor +import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map /** Provides a state for the Volume Dialog. */ @@ -38,18 +41,21 @@ constructor( private val context: Context, dialogVisibilityInteractor: VolumeDialogVisibilityInteractor, volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor, - private val volumeDialogSliderInteractorFactory: VolumeDialogSliderInteractor.Factory, + volumeDialogStateInteractor: VolumeDialogStateInteractor, ) { val dialogVisibilityModel: Flow<VolumeDialogVisibilityModel> = dialogVisibilityInteractor.dialogVisibility val dialogTitle: Flow<String> = - volumeDialogSlidersInteractor.sliders.flatMapLatest { slidersModel -> - val interactor = volumeDialogSliderInteractorFactory.create(slidersModel.slider) - interactor.slider.map { sliderModel -> - context.getString(R.string.volume_dialog_title, sliderModel.streamLabel(context)) + combine( + volumeDialogStateInteractor.volumeDialogState, + volumeDialogSlidersInteractor.sliders.map { it.slider }, + ) { state: VolumeDialogStateModel, sliderType: VolumeDialogSliderType -> + state.streamModels[sliderType.audioStream]?.let { model -> + context.getString(R.string.volume_dialog_title, model.streamLabel(context)) + } } - } + .filterNotNull() @AssistedFactory interface Factory { diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json index aa8044515ea2..aa8044515ea2 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json index a840d3cb1225..7abff2c74531 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json @@ -33,118 +33,118 @@ "bottom": 0 }, { - "left": 94, - "top": 284, - "right": 206, + "left": 104, + "top": 285, + "right": 215, "bottom": 414 }, { - "left": 83, - "top": 251, - "right": 219, + "left": 92, + "top": 252, + "right": 227, "bottom": 447 }, { - "left": 70, - "top": 212, - "right": 234, - "bottom": 485 + "left": 77, + "top": 213, + "right": 242, + "bottom": 486 }, { - "left": 57, - "top": 173, - "right": 250, - "bottom": 522 + "left": 63, + "top": 175, + "right": 256, + "bottom": 524 }, { - "left": 46, - "top": 139, - "right": 264, - "bottom": 555 + "left": 50, + "top": 141, + "right": 269, + "bottom": 558 }, { - "left": 36, - "top": 109, - "right": 276, - "bottom": 584 + "left": 40, + "top": 112, + "right": 279, + "bottom": 587 }, { - "left": 28, - "top": 84, - "right": 285, - "bottom": 608 + "left": 31, + "top": 88, + "right": 288, + "bottom": 611 }, { - "left": 21, - "top": 65, - "right": 293, - "bottom": 627 + "left": 23, + "top": 68, + "right": 296, + "bottom": 631 }, { - "left": 16, - "top": 49, - "right": 300, - "bottom": 642 + "left": 18, + "top": 53, + "right": 301, + "bottom": 646 }, { - "left": 12, - "top": 36, - "right": 305, - "bottom": 653 + "left": 13, + "top": 41, + "right": 306, + "bottom": 658 }, { - "left": 9, - "top": 27, - "right": 308, - "bottom": 662 + "left": 10, + "top": 31, + "right": 309, + "bottom": 667 }, { "left": 7, - "top": 20, + "top": 24, "right": 312, - "bottom": 669 + "bottom": 673 }, { "left": 5, - "top": 14, + "top": 18, "right": 314, - "bottom": 675 + "bottom": 678 }, { "left": 4, - "top": 11, + "top": 13, "right": 315, - "bottom": 678 + "bottom": 681 }, { "left": 3, - "top": 8, + "top": 10, "right": 316, - "bottom": 681 + "bottom": 684 }, { "left": 2, - "top": 5, + "top": 7, "right": 317, - "bottom": 684 + "bottom": 685 }, { "left": 1, - "top": 4, + "top": 5, "right": 318, - "bottom": 685 + "bottom": 687 }, { "left": 1, - "top": 3, + "top": 4, "right": 318, - "bottom": 686 + "bottom": 688 }, { "left": 0, - "top": 2, + "top": 3, "right": 319, - "bottom": 687 + "bottom": 688 } ] }, @@ -371,5 +371,14 @@ 0 ] } - ] + ], + "\/\/metadata": { + "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenLaunching_withSpring.json", + "goldenIdentifier": "backgroundAnimation_whenLaunching_withSpring", + "testClassName": "TransitionAnimatorTest", + "testMethodName": "backgroundAnimation_whenLaunching[true]", + "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots", + "result": "FAILED", + "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenLaunching_withSpring.actual.mp4" + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json index aa8044515ea2..aa8044515ea2 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json index a840d3cb1225..561961145ca1 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json @@ -33,118 +33,118 @@ "bottom": 0 }, { - "left": 94, - "top": 284, - "right": 206, + "left": 104, + "top": 285, + "right": 215, "bottom": 414 }, { - "left": 83, - "top": 251, - "right": 219, + "left": 92, + "top": 252, + "right": 227, "bottom": 447 }, { - "left": 70, - "top": 212, - "right": 234, - "bottom": 485 + "left": 77, + "top": 213, + "right": 242, + "bottom": 486 }, { - "left": 57, - "top": 173, - "right": 250, - "bottom": 522 + "left": 63, + "top": 175, + "right": 256, + "bottom": 524 }, { - "left": 46, - "top": 139, - "right": 264, - "bottom": 555 + "left": 50, + "top": 141, + "right": 269, + "bottom": 558 }, { - "left": 36, - "top": 109, - "right": 276, - "bottom": 584 + "left": 40, + "top": 112, + "right": 279, + "bottom": 587 }, { - "left": 28, - "top": 84, - "right": 285, - "bottom": 608 + "left": 31, + "top": 88, + "right": 288, + "bottom": 611 }, { - "left": 21, - "top": 65, - "right": 293, - "bottom": 627 + "left": 23, + "top": 68, + "right": 296, + "bottom": 631 }, { - "left": 16, - "top": 49, - "right": 300, - "bottom": 642 + "left": 18, + "top": 53, + "right": 301, + "bottom": 646 }, { - "left": 12, - "top": 36, - "right": 305, - "bottom": 653 + "left": 13, + "top": 41, + "right": 306, + "bottom": 658 }, { - "left": 9, - "top": 27, - "right": 308, - "bottom": 662 + "left": 10, + "top": 31, + "right": 309, + "bottom": 667 }, { "left": 7, - "top": 20, + "top": 24, "right": 312, - "bottom": 669 + "bottom": 673 }, { "left": 5, - "top": 14, + "top": 18, "right": 314, - "bottom": 675 + "bottom": 678 }, { "left": 4, - "top": 11, + "top": 13, "right": 315, - "bottom": 678 + "bottom": 681 }, { "left": 3, - "top": 8, + "top": 10, "right": 316, - "bottom": 681 + "bottom": 684 }, { "left": 2, - "top": 5, + "top": 7, "right": 317, - "bottom": 684 + "bottom": 685 }, { "left": 1, - "top": 4, + "top": 5, "right": 318, - "bottom": 685 + "bottom": 687 }, { "left": 1, - "top": 3, + "top": 4, "right": 318, - "bottom": 686 + "bottom": 688 }, { "left": 0, - "top": 2, + "top": 3, "right": 319, - "bottom": 687 + "bottom": 688 } ] }, @@ -371,5 +371,14 @@ 0 ] } - ] + ], + "\/\/metadata": { + "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenReturning_withSpring.json", + "goldenIdentifier": "backgroundAnimation_whenReturning_withSpring", + "testClassName": "TransitionAnimatorTest", + "testMethodName": "backgroundAnimation_whenReturning[true]", + "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots", + "result": "FAILED", + "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenReturning_withSpring.actual.mp4" + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json index 7f623575fef4..7f623575fef4 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json index 18eedd450751..825190ba7a32 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json @@ -33,118 +33,118 @@ "bottom": 0 }, { - "left": 94, - "top": 284, - "right": 206, + "left": 104, + "top": 285, + "right": 215, "bottom": 414 }, { - "left": 83, - "top": 251, - "right": 219, + "left": 92, + "top": 252, + "right": 227, "bottom": 447 }, { - "left": 70, - "top": 212, - "right": 234, - "bottom": 485 + "left": 77, + "top": 213, + "right": 242, + "bottom": 486 }, { - "left": 57, - "top": 173, - "right": 250, - "bottom": 522 + "left": 63, + "top": 175, + "right": 256, + "bottom": 524 }, { - "left": 46, - "top": 139, - "right": 264, - "bottom": 555 + "left": 50, + "top": 141, + "right": 269, + "bottom": 558 }, { - "left": 36, - "top": 109, - "right": 276, - "bottom": 584 + "left": 40, + "top": 112, + "right": 279, + "bottom": 587 }, { - "left": 28, - "top": 84, - "right": 285, - "bottom": 608 + "left": 31, + "top": 88, + "right": 288, + "bottom": 611 }, { - "left": 21, - "top": 65, - "right": 293, - "bottom": 627 + "left": 23, + "top": 68, + "right": 296, + "bottom": 631 }, { - "left": 16, - "top": 49, - "right": 300, - "bottom": 642 + "left": 18, + "top": 53, + "right": 301, + "bottom": 646 }, { - "left": 12, - "top": 36, - "right": 305, - "bottom": 653 + "left": 13, + "top": 41, + "right": 306, + "bottom": 658 }, { - "left": 9, - "top": 27, - "right": 308, - "bottom": 662 + "left": 10, + "top": 31, + "right": 309, + "bottom": 667 }, { "left": 7, - "top": 20, + "top": 24, "right": 312, - "bottom": 669 + "bottom": 673 }, { "left": 5, - "top": 14, + "top": 18, "right": 314, - "bottom": 675 + "bottom": 678 }, { "left": 4, - "top": 11, + "top": 13, "right": 315, - "bottom": 678 + "bottom": 681 }, { "left": 3, - "top": 8, + "top": 10, "right": 316, - "bottom": 681 + "bottom": 684 }, { "left": 2, - "top": 5, + "top": 7, "right": 317, - "bottom": 684 + "bottom": 685 }, { "left": 1, - "top": 4, + "top": 5, "right": 318, - "bottom": 685 + "bottom": 687 }, { "left": 1, - "top": 3, + "top": 4, "right": 318, - "bottom": 686 + "bottom": 688 }, { "left": 0, - "top": 2, + "top": 3, "right": 319, - "bottom": 687 + "bottom": 688 } ] }, @@ -371,5 +371,14 @@ 0 ] } - ] + ], + "\/\/metadata": { + "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenLaunching_withSpring.json", + "goldenIdentifier": "backgroundAnimationWithoutFade_whenLaunching_withSpring", + "testClassName": "TransitionAnimatorTest", + "testMethodName": "backgroundAnimationWithoutFade_whenLaunching[true]", + "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots", + "result": "FAILED", + "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenLaunching_withSpring.actual.mp4" + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json index 98005c53f6e0..98005c53f6e0 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json index 18eedd450751..63c263175122 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json @@ -33,118 +33,118 @@ "bottom": 0 }, { - "left": 94, - "top": 284, - "right": 206, + "left": 104, + "top": 285, + "right": 215, "bottom": 414 }, { - "left": 83, - "top": 251, - "right": 219, + "left": 92, + "top": 252, + "right": 227, "bottom": 447 }, { - "left": 70, - "top": 212, - "right": 234, - "bottom": 485 + "left": 77, + "top": 213, + "right": 242, + "bottom": 486 }, { - "left": 57, - "top": 173, - "right": 250, - "bottom": 522 + "left": 63, + "top": 175, + "right": 256, + "bottom": 524 }, { - "left": 46, - "top": 139, - "right": 264, - "bottom": 555 + "left": 50, + "top": 141, + "right": 269, + "bottom": 558 }, { - "left": 36, - "top": 109, - "right": 276, - "bottom": 584 + "left": 40, + "top": 112, + "right": 279, + "bottom": 587 }, { - "left": 28, - "top": 84, - "right": 285, - "bottom": 608 + "left": 31, + "top": 88, + "right": 288, + "bottom": 611 }, { - "left": 21, - "top": 65, - "right": 293, - "bottom": 627 + "left": 23, + "top": 68, + "right": 296, + "bottom": 631 }, { - "left": 16, - "top": 49, - "right": 300, - "bottom": 642 + "left": 18, + "top": 53, + "right": 301, + "bottom": 646 }, { - "left": 12, - "top": 36, - "right": 305, - "bottom": 653 + "left": 13, + "top": 41, + "right": 306, + "bottom": 658 }, { - "left": 9, - "top": 27, - "right": 308, - "bottom": 662 + "left": 10, + "top": 31, + "right": 309, + "bottom": 667 }, { "left": 7, - "top": 20, + "top": 24, "right": 312, - "bottom": 669 + "bottom": 673 }, { "left": 5, - "top": 14, + "top": 18, "right": 314, - "bottom": 675 + "bottom": 678 }, { "left": 4, - "top": 11, + "top": 13, "right": 315, - "bottom": 678 + "bottom": 681 }, { "left": 3, - "top": 8, + "top": 10, "right": 316, - "bottom": 681 + "bottom": 684 }, { "left": 2, - "top": 5, + "top": 7, "right": 317, - "bottom": 684 + "bottom": 685 }, { "left": 1, - "top": 4, + "top": 5, "right": 318, - "bottom": 685 + "bottom": 687 }, { "left": 1, - "top": 3, + "top": 4, "right": 318, - "bottom": 686 + "bottom": 688 }, { "left": 0, - "top": 2, + "top": 3, "right": 319, - "bottom": 687 + "bottom": 688 } ] }, @@ -371,5 +371,14 @@ 0 ] } - ] + ], + "\/\/metadata": { + "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenReturning_withSpring.json", + "goldenIdentifier": "backgroundAnimationWithoutFade_whenReturning_withSpring", + "testClassName": "TransitionAnimatorTest", + "testMethodName": "backgroundAnimationWithoutFade_whenReturning[true]", + "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots", + "result": "FAILED", + "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenReturning_withSpring.actual.mp4" + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java index c6e4e0db8945..fa88f6207c49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java @@ -46,7 +46,6 @@ import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.View; import android.view.WindowManager; -import android.view.WindowManagerGlobal; import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.animation.AccelerateInterpolator; import android.window.InputTransferToken; @@ -1030,10 +1029,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { callback, sysUiState, secureSettings, - scvhSupplier, - sfVsyncFrameProvider, - WindowManagerGlobal::getWindowSession, - viewCaptureAwareWindowManager); + scvhSupplier); mSpyController = Mockito.mock(WindowMagnificationController.class); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java deleted file mode 100644 index 9b09ec2ec55f..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java +++ /dev/null @@ -1,1594 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.accessibility; - -import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.content.res.Configuration.ORIENTATION_UNDEFINED; -import static android.view.MotionEvent.ACTION_DOWN; -import static android.view.MotionEvent.ACTION_UP; -import static android.view.WindowInsets.Type.systemGestures; -import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; - -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.AdditionalAnswers.returnsSecondArg; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyFloat; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import static java.util.Arrays.asList; - -import android.animation.ValueAnimator; -import android.annotation.IdRes; -import android.annotation.Nullable; -import android.app.Instrumentation; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Insets; -import android.graphics.PointF; -import android.graphics.Rect; -import android.os.Handler; -import android.os.RemoteException; -import android.os.SystemClock; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; -import android.provider.Settings; -import android.testing.TestableLooper; -import android.testing.TestableResources; -import android.util.Size; -import android.view.AttachedSurfaceControl; -import android.view.Display; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.SurfaceControl; -import android.view.SurfaceControlViewHost; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewRootImpl; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.widget.FrameLayout; -import android.window.InputTransferToken; - -import androidx.test.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.LargeTest; - -import com.android.app.viewcapture.ViewCaptureAwareWindowManager; -import com.android.systemui.Flags; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.animation.AnimatorTestRule; -import com.android.systemui.kosmos.KosmosJavaAdapter; -import com.android.systemui.model.SysUiState; -import com.android.systemui.res.R; -import com.android.systemui.settings.FakeDisplayTracker; -import com.android.systemui.util.FakeSharedPreferences; -import com.android.systemui.util.leak.ReferenceTestUtils; -import com.android.systemui.util.settings.SecureSettings; -import com.android.systemui.utils.os.FakeHandler; - -import com.google.common.util.concurrent.AtomicDouble; - -import org.junit.After; -import org.junit.Assume; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; - -@LargeTest -@TestableLooper.RunWithLooper -@RunWith(AndroidJUnit4.class) -@EnableFlags(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER) -public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiTestCase { - - @Rule - // NOTE: pass 'null' to allow this test advances time on the main thread. - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null); - - private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000; - @Mock - private MirrorWindowControl mMirrorWindowControl; - @Mock - private WindowMagnifierCallback mWindowMagnifierCallback; - @Mock - IRemoteMagnificationAnimationCallback mAnimationCallback; - @Mock - IRemoteMagnificationAnimationCallback mAnimationCallback2; - - private SurfaceControl.Transaction mTransaction; - @Mock - private SecureSettings mSecureSettings; - @Mock - private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager; - - private long mWaitAnimationDuration; - private long mWaitBounceEffectDuration; - - private Handler mHandler; - private TestableWindowManager mWindowManager; - private SysUiState mSysUiState; - private Resources mResources; - private WindowMagnificationAnimationController mWindowMagnificationAnimationController; - private WindowMagnificationController mWindowMagnificationController; - private Instrumentation mInstrumentation; - private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0); - private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); - - private View mSpyView; - private View.OnTouchListener mTouchListener; - - private MotionEventHelper mMotionEventHelper = new MotionEventHelper(); - - // This list contains all SurfaceControlViewHosts created during a given test. If the - // magnification window is recreated during a test, the list will contain more than a single - // element. - private List<SurfaceControlViewHost> mSurfaceControlViewHosts = new ArrayList<>(); - // The most recently created SurfaceControlViewHost. - private SurfaceControlViewHost mSurfaceControlViewHost; - private KosmosJavaAdapter mKosmos; - private FakeSharedPreferences mSharedPreferences; - - /** - * return whether window magnification is supported for current test context. - */ - private boolean isWindowModeSupported() { - return getContext().getPackageManager().hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION); - } - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mContext = spy(mContext); - mKosmos = new KosmosJavaAdapter(this); - mContext = Mockito.spy(getContext()); - mHandler = new FakeHandler(TestableLooper.get(this).getLooper()); - mInstrumentation = InstrumentationRegistry.getInstrumentation(); - final WindowManager wm = mContext.getSystemService(WindowManager.class); - mWindowManager = spy(new TestableWindowManager(wm)); - - mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); - mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin()); - mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class)); - when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then( - returnsSecondArg()); - when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then( - returnsSecondArg()); - - mResources = getContext().getOrCreateTestableResources().getResources(); - // prevent the config orientation from undefined, which may cause config.diff method - // neglecting the orientation update. - if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) { - mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT; - } - - // Using the animation duration in WindowMagnificationAnimationController for testing. - mWaitAnimationDuration = mResources.getInteger( - com.android.internal.R.integer.config_longAnimTime); - // Using the bounce effect duration in WindowMagnificationController for testing. - mWaitBounceEffectDuration = mResources.getInteger( - com.android.internal.R.integer.config_shortAnimTime); - - mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( - mContext, mValueAnimator); - Supplier<SurfaceControlViewHost> scvhSupplier = () -> { - mSurfaceControlViewHost = spy(new SurfaceControlViewHost( - mContext, mContext.getDisplay(), new InputTransferToken(), - "WindowMagnification")); - ViewRootImpl viewRoot = mock(ViewRootImpl.class); - when(mSurfaceControlViewHost.getRootSurfaceControl()).thenReturn(viewRoot); - mSurfaceControlViewHosts.add(mSurfaceControlViewHost); - return mSurfaceControlViewHost; - }; - mTransaction = spy(new SurfaceControl.Transaction()); - mSharedPreferences = new FakeSharedPreferences(); - when(mContext.getSharedPreferences( - eq("window_magnification_preferences"), anyInt())) - .thenReturn(mSharedPreferences); - mWindowMagnificationController = - new WindowMagnificationController( - mContext, - mHandler, - mWindowMagnificationAnimationController, - mMirrorWindowControl, - mTransaction, - mWindowMagnifierCallback, - mSysUiState, - mSecureSettings, - scvhSupplier, - /* sfVsyncFrameProvider= */ null, - /* globalWindowSessionSupplier= */ null, - mViewCaptureAwareWindowManager); - - verify(mMirrorWindowControl).setWindowDelegate( - any(MirrorWindowControl.MirrorWindowDelegate.class)); - mSpyView = Mockito.spy(new View(mContext)); - doAnswer((invocation) -> { - mTouchListener = invocation.getArgument(0); - return null; - }).when(mSpyView).setOnTouchListener( - any(View.OnTouchListener.class)); - - // skip test if window magnification is not supported to prevent fail results. (b/279820875) - Assume.assumeTrue(isWindowModeSupported()); - } - - @After - public void tearDown() { - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.deleteWindowMagnification(); - }); - mValueAnimator.cancel(); - } - - @Test - public void initWindowMagnificationController_checkAllowDiagonalScrollingWithSecureSettings() { - verify(mSecureSettings).getIntForUser( - eq(Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING), - /* def */ eq(1), /* userHandle= */ anyInt()); - assertThat(mWindowMagnificationController.isDiagonalScrollingEnabled()).isTrue(); - } - - @Test - public void enableWindowMagnification_showControlAndNotifyBoundsChanged() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - verify(mMirrorWindowControl).showControl(); - verify(mWindowMagnifierCallback, - timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeastOnce()).onWindowMagnifierBoundsChanged( - eq(mContext.getDisplayId()), any(Rect.class)); - } - - @Test - public void enableWindowMagnification_notifySourceBoundsChanged() { - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, - Float.NaN, /* magnificationFrameOffsetRatioX= */ 0, - /* magnificationFrameOffsetRatioY= */ 0, null)); - - // Waits for the surface created - verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)).onSourceBoundsChanged( - (eq(mContext.getDisplayId())), any()); - } - - @Test - public void enableWindowMagnification_disabled_notifySourceBoundsChanged() { - enableWindowMagnification_notifySourceBoundsChanged(); - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.deleteWindowMagnification(null)); - Mockito.reset(mWindowMagnifierCallback); - - enableWindowMagnification_notifySourceBoundsChanged(); - } - - @Test - public void enableWindowMagnification_withAnimation_schedulesFrame() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(2.0f, 10, - 10, /* magnificationFrameOffsetRatioX= */ 0, - /* magnificationFrameOffsetRatioY= */ 0, - Mockito.mock(IRemoteMagnificationAnimationCallback.class)); - }); - advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS); - - verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(), - eq(Surface.ROTATION_0)); - } - - @Test - public void moveWindowMagnifier_enabled_notifySourceBoundsChanged() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, - Float.NaN, 0, 0, null); - }); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.moveWindowMagnifier(10, 10); - }); - - final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); - verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged( - (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - assertThat(mWindowMagnificationController.getCenterX()) - .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX()); - assertThat(mWindowMagnificationController.getCenterY()) - .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY()); - } - - @Test - public void enableWindowMagnification_systemGestureExclusionRectsIsSet() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - // Wait for Rects updated. - waitForIdleSync(); - - List<Rect> rects = mSurfaceControlViewHost.getView().getSystemGestureExclusionRects(); - assertThat(rects).isNotEmpty(); - } - - @Ignore("The default window size should be constrained after fixing b/288056772") - @Test - public void enableWindowMagnification_LargeScreen_windowSizeIsConstrained() { - final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10; - mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize)); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - final int halfScreenSize = screenSize / 2; - ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); - // The frame size should be the half of smaller value of window height/width unless it - //exceed the max frame size. - assertThat(params.width).isLessThan(halfScreenSize); - assertThat(params.height).isLessThan(halfScreenSize); - } - - @Test - public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() { - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, - Float.NaN)); - - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.deleteWindowMagnification()); - - verify(mMirrorWindowControl).destroyControl(); - verify(mContext).unregisterComponentCallbacks(mWindowMagnificationController); - } - - @Test - public void deleteWindowMagnification_enableAtTheBottom_overlapFlagIsFalse() { - final WindowManager wm = mContext.getSystemService(WindowManager.class); - final Rect bounds = wm.getCurrentWindowMetrics().getBounds(); - setSystemGestureInsets(); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - bounds.bottom); - }); - ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.deleteWindowMagnification(); - }); - - verify(mMirrorWindowControl).destroyControl(); - assertThat(hasMagnificationOverlapFlag()).isFalse(); - } - - @Test - public void deleteWindowMagnification_notifySourceBoundsChanged() { - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, - Float.NaN)); - - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.deleteWindowMagnification()); - - // The first time is for notifying magnification enabled and the second time is for - // notifying magnification disabled. - verify(mWindowMagnifierCallback, times(2)).onSourceBoundsChanged( - (eq(mContext.getDisplayId())), any()); - } - - @Test - public void moveMagnifier_schedulesFrame() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - waitForIdleSync(); - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.moveWindowMagnifier(100f, 100f)); - - verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(), - eq(Surface.ROTATION_0)); - } - - @Test - public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback() - throws RemoteException { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, - Float.NaN, 0, 0, null); - }); - - final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); - verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) - .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10; - final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10; - - reset(mWindowMagnifierCallback); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.moveWindowMagnifierToPosition( - targetCenterX, targetCenterY, mAnimationCallback); - }); - advanceTimeBy(mWaitAnimationDuration); - - verify(mAnimationCallback, times(1)).onResult(eq(true)); - verify(mAnimationCallback, never()).onResult(eq(false)); - verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) - .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - assertThat(mWindowMagnificationController.getCenterX()) - .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX()); - assertThat(mWindowMagnificationController.getCenterY()) - .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY()); - assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(targetCenterX); - assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(targetCenterY); - } - - @Test - public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback() - throws RemoteException { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, - Float.NaN, 0, 0, null); - }); - - final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); - verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) - .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - final float centerX = sourceBoundsCaptor.getValue().exactCenterX(); - final float centerY = sourceBoundsCaptor.getValue().exactCenterY(); - - reset(mWindowMagnifierCallback); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 10, centerY + 10, mAnimationCallback); - mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 20, centerY + 20, mAnimationCallback); - mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 30, centerY + 30, mAnimationCallback); - mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 40, centerY + 40, mAnimationCallback2); - }); - advanceTimeBy(mWaitAnimationDuration); - - // only the last one callback will return true - verify(mAnimationCallback2).onResult(eq(true)); - // the others will return false - verify(mAnimationCallback, times(3)).onResult(eq(false)); - verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) - .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - assertThat(mWindowMagnificationController.getCenterX()) - .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX()); - assertThat(mWindowMagnificationController.getCenterY()) - .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY()); - assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(centerX + 40); - assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(centerY + 40); - } - - @Test - public void setScale_enabled_expectedValueAndUpdateStateDescription() { - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(2.0f, - Float.NaN, Float.NaN)); - - mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f)); - - assertThat(mWindowMagnificationController.getScale()).isEqualTo(3.0f); - final View mirrorView = mSurfaceControlViewHost.getView(); - assertThat(mirrorView).isNotNull(); - assertThat(mirrorView.getStateDescription().toString()).contains("300"); - } - - @Test - public void onConfigurationChanged_disabled_withoutException() { - Display display = Mockito.spy(mContext.getDisplay()); - when(display.getRotation()).thenReturn(Surface.ROTATION_90); - when(mContext.getDisplay()).thenReturn(display); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY); - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION); - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE); - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); - }); - } - - @Test - public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() { - final int newRotation = simulateRotateTheDevice(); - final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds()); - final float center = Math.min(windowBounds.exactCenterX(), windowBounds.exactCenterY()); - final float displayWidth = windowBounds.width(); - final PointF magnifiedCenter = new PointF(center, center + 5f); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - magnifiedCenter.x, magnifiedCenter.y); - // Get the center again in case the center we set is out of screen. - magnifiedCenter.set(mWindowMagnificationController.getCenterX(), - mWindowMagnificationController.getCenterY()); - }); - // Rotate the window clockwise 90 degree. - windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom, - windowBounds.right); - mWindowManager.setWindowBounds(windowBounds); - - mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged( - ActivityInfo.CONFIG_ORIENTATION)); - - assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation); - final PointF expectedCenter = new PointF(magnifiedCenter.y, - displayWidth - magnifiedCenter.x); - final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(), - mWindowMagnificationController.getCenterY()); - assertThat(actualCenter).isEqualTo(expectedCenter); - } - - @Test - public void onOrientationChanged_disabled_updateDisplayRotation() { - final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds()); - // Rotate the window clockwise 90 degree. - windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom, - windowBounds.right); - mWindowManager.setWindowBounds(windowBounds); - final int newRotation = simulateRotateTheDevice(); - - mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged( - ActivityInfo.CONFIG_ORIENTATION)); - - assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation); - } - - @Test - public void onScreenSizeAndDensityChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() { - // The default position is at the center of the screen. - final float expectedRatio = 0.5f; - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - // Screen size and density change - mContext.getResources().getConfiguration().smallestScreenWidthDp = - mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; - final Rect testWindowBounds = new Rect( - mWindowManager.getCurrentWindowMetrics().getBounds()); - testWindowBounds.set(testWindowBounds.left, testWindowBounds.top, - testWindowBounds.right + 100, testWindowBounds.bottom + 100); - mWindowManager.setWindowBounds(testWindowBounds); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); - }); - - // The ratio of center to window size should be the same. - assertThat(mWindowMagnificationController.getCenterX() / testWindowBounds.width()) - .isEqualTo(expectedRatio); - assertThat(mWindowMagnificationController.getCenterY() / testWindowBounds.height()) - .isEqualTo(expectedRatio); - } - - @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) - @Test - public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() { - int newSmallestScreenWidthDp = - mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; - int windowFrameSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize); - mSharedPreferences - .edit() - .putString(String.valueOf(newSmallestScreenWidthDp), - preferredWindowSize.toString()) - .commit(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - // Screen density and size change - mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp; - final Rect testWindowBounds = new Rect( - mWindowManager.getCurrentWindowMetrics().getBounds()); - testWindowBounds.set(testWindowBounds.left, testWindowBounds.top, - testWindowBounds.right + 100, testWindowBounds.bottom + 100); - mWindowManager.setWindowBounds(testWindowBounds); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); - }); - - // wait for rect update - waitForIdleSync(); - ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); - final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - // The width and height of the view include the magnification frame and the margins. - assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); - assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); - } - - @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) - @Test - public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() { - int newSmallestScreenWidthDp = - mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; - int windowFrameSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize); - mSharedPreferences - .edit() - .putString(String.valueOf(newSmallestScreenWidthDp), - WindowMagnificationFrameSpec.serialize( - WindowMagnificationSettings.MagnificationSize.CUSTOM, - preferredWindowSize)) - .commit(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - // Screen density and size change - mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp; - final Rect testWindowBounds = new Rect( - mWindowManager.getCurrentWindowMetrics().getBounds()); - testWindowBounds.set(testWindowBounds.left, testWindowBounds.top, - testWindowBounds.right + 100, testWindowBounds.bottom + 100); - mWindowManager.setWindowBounds(testWindowBounds); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); - }); - - // wait for rect update - waitForIdleSync(); - verify(mWindowMagnifierCallback).onWindowMagnifierBoundsRestored( - eq(mContext.getDisplayId()), - eq(WindowMagnificationSettings.MagnificationSize.CUSTOM)); - ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); - final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - // The width and height of the view include the magnification frame and the margins. - assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); - assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); - } - - @Test - public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10; - // Screen size and density change - mContext.getResources().getConfiguration().smallestScreenWidthDp = - mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; - mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize)); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); - }); - - final int defaultWindowSize = - mWindowMagnificationController.getMagnificationWindowSizeFromIndex( - WindowMagnificationSettings.MagnificationSize.MEDIUM); - ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); - - assertThat(params.width).isEqualTo(defaultWindowSize); - assertThat(params.height).isEqualTo(defaultWindowSize); - } - - @Test - public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - Mockito.reset(mWindowManager); - Mockito.reset(mMirrorWindowControl); - }); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY); - }); - - verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt()); - verify(mSurfaceControlViewHosts.get(0)).release(); - verify(mMirrorWindowControl).destroyControl(); - verify(mSurfaceControlViewHosts.get(1)).setView(any(), any()); - verify(mMirrorWindowControl).showControl(); - } - - @Test - public void onDensityChanged_disabled_updateDimensions() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY); - }); - - verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt()); - } - - @Test - public void initializeA11yNode_enabled_expectedValues() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN, - Float.NaN); - }); - final View mirrorView = mSurfaceControlViewHost.getView(); - assertThat(mirrorView).isNotNull(); - final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo(); - - mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo); - - assertThat(nodeInfo.getContentDescription()).isNotNull(); - assertThat(nodeInfo.getStateDescription().toString()).contains("250"); - assertThat(nodeInfo.getActionList()).containsExactlyElementsIn(asList( - new AccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), - mContext.getResources().getString( - R.string.magnification_open_settings_click_label)), - new AccessibilityAction(R.id.accessibility_action_zoom_in, - mContext.getString(R.string.accessibility_control_zoom_in)), - new AccessibilityAction(R.id.accessibility_action_zoom_out, - mContext.getString(R.string.accessibility_control_zoom_out)), - new AccessibilityAction(R.id.accessibility_action_move_right, - mContext.getString(R.string.accessibility_control_move_right)), - new AccessibilityAction(R.id.accessibility_action_move_left, - mContext.getString(R.string.accessibility_control_move_left)), - new AccessibilityAction(R.id.accessibility_action_move_down, - mContext.getString(R.string.accessibility_control_move_down)), - new AccessibilityAction(R.id.accessibility_action_move_up, - mContext.getString(R.string.accessibility_control_move_up)))); - } - - @Test - public void performA11yActions_visible_expectedResults() { - final int displayId = mContext.getDisplayId(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(1.5f, Float.NaN, - Float.NaN); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null)) - .isTrue(); - // Minimum scale is 1.0. - verify(mWindowMagnifierCallback).onPerformScaleAction( - eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true)); - - assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null)) - .isTrue(); - verify(mWindowMagnifierCallback).onPerformScaleAction( - eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true)); - - // TODO: Verify the final state when the mirror surface is visible. - assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null)) - .isTrue(); - assertThat( - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null)) - .isTrue(); - assertThat( - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null)) - .isTrue(); - assertThat( - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null)) - .isTrue(); - verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId)); - - assertThat(mirrorView.performAccessibilityAction( - AccessibilityAction.ACTION_CLICK.getId(), null)).isTrue(); - verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId)); - } - - @Test - public void performA11yActions_visible_notifyAccessibilityActionPerformed() { - final int displayId = mContext.getDisplayId(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN, - Float.NaN); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null); - - verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId)); - } - - @Test - public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - View closeButton = getInternalView(R.id.close_button); - View bottomRightCorner = getInternalView(R.id.bottom_right_corner); - View bottomLeftCorner = getInternalView(R.id.bottom_left_corner); - View topRightCorner = getInternalView(R.id.top_right_corner); - View topLeftCorner = getInternalView(R.id.top_left_corner); - - assertThat(closeButton.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(topRightCorner.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(topLeftCorner.getVisibility()).isEqualTo(View.VISIBLE); - - final View mirrorView = mSurfaceControlViewHost.getView(); - mInstrumentation.runOnMainSync(() -> - mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), - null)); - - assertThat(closeButton.getVisibility()).isEqualTo(View.GONE); - assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.GONE); - assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.GONE); - assertThat(topRightCorner.getVisibility()).isEqualTo(View.GONE); - assertThat(topLeftCorner.getVisibility()).isEqualTo(View.GONE); - } - - @Test - - public void windowWidthIsNotMax_performA11yActionIncreaseWidth_windowWidthIncreased() { - final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - final int startingWidth = (int) (windowBounds.width() * 0.8); - final int startingHeight = (int) (windowBounds.height() * 0.8); - final float changeWindowSizeAmount = mContext.getResources().getFraction( - R.fraction.magnification_resize_window_size_amount, - /* base= */ 1, - /* pbase= */ 1); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingWidth, startingHeight); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - - mInstrumentation.runOnMainSync( - () -> { - mirrorView.performAccessibilityAction( - R.id.accessibility_action_increase_window_width, null); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - // Window width includes the magnifier frame and the margin. Increasing the window size - // will be increasing the amount of the frame size only. - int newWindowWidth = - (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount)) - + 2 * mirrorSurfaceMargin; - assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth); - assertThat(actualWindowHeight.get()).isEqualTo(startingHeight); - } - - @Test - public void windowHeightIsNotMax_performA11yActionIncreaseHeight_windowHeightIncreased() { - final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - final int startingWidth = (int) (windowBounds.width() * 0.8); - final int startingHeight = (int) (windowBounds.height() * 0.8); - final float changeWindowSizeAmount = mContext.getResources().getFraction( - R.fraction.magnification_resize_window_size_amount, - /* base= */ 1, - /* pbase= */ 1); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingWidth, startingHeight); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - - mInstrumentation.runOnMainSync( - () -> { - mirrorView.performAccessibilityAction( - R.id.accessibility_action_increase_window_height, null); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - // Window height includes the magnifier frame and the margin. Increasing the window size - // will be increasing the amount of the frame size only. - int newWindowHeight = - (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount)) - + 2 * mirrorSurfaceMargin; - assertThat(actualWindowWidth.get()).isEqualTo(startingWidth); - assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight); - } - - @Test - public void windowWidthIsMax_noIncreaseWindowWidthA11yAction() { - final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - final int startingWidth = windowBounds.width(); - final int startingHeight = windowBounds.height(); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingWidth, startingHeight); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AccessibilityNodeInfo accessibilityNodeInfo = - mirrorView.createAccessibilityNodeInfo(); - assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( - new AccessibilityAction( - R.id.accessibility_action_increase_window_width, - mContext.getString( - R.string.accessibility_control_increase_window_width))); - } - - @Test - public void windowHeightIsMax_noIncreaseWindowHeightA11yAction() { - final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - final int startingWidth = windowBounds.width(); - final int startingHeight = windowBounds.height(); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingWidth, startingHeight); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AccessibilityNodeInfo accessibilityNodeInfo = - mirrorView.createAccessibilityNodeInfo(); - assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( - new AccessibilityAction( - R.id.accessibility_action_increase_window_height, null)); - } - - @Test - public void windowWidthIsNotMin_performA11yActionDecreaseWidth_windowWidthDecreased() { - int mMinWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final int startingSize = (int) (mMinWindowSize * 1.1); - final float changeWindowSizeAmount = mContext.getResources().getFraction( - R.fraction.magnification_resize_window_size_amount, - /* base= */ 1, - /* pbase= */ 1); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingSize, startingSize); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - - mInstrumentation.runOnMainSync( - () -> { - mirrorView.performAccessibilityAction( - R.id.accessibility_action_decrease_window_width, null); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - // Window width includes the magnifier frame and the margin. Decreasing the window size - // will be decreasing the amount of the frame size only. - int newWindowWidth = - (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount)) - + 2 * mirrorSurfaceMargin; - assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth); - assertThat(actualWindowHeight.get()).isEqualTo(startingSize); - } - - @Test - public void windowHeightIsNotMin_performA11yActionDecreaseHeight_windowHeightDecreased() { - int mMinWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final int startingSize = (int) (mMinWindowSize * 1.1); - final float changeWindowSizeAmount = mContext.getResources().getFraction( - R.fraction.magnification_resize_window_size_amount, - /* base= */ 1, - /* pbase= */ 1); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingSize, startingSize); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - - mInstrumentation.runOnMainSync( - () -> { - mirrorView.performAccessibilityAction( - R.id.accessibility_action_decrease_window_height, null); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - // Window height includes the magnifier frame and the margin. Decreasing the window size - // will be decreasing the amount of the frame size only. - int newWindowHeight = - (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount)) - + 2 * mirrorSurfaceMargin; - assertThat(actualWindowWidth.get()).isEqualTo(startingSize); - assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight); - } - - @Test - public void windowWidthIsMin_noDecreaseWindowWidthA11yAction() { - int mMinWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final int startingSize = mMinWindowSize; - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingSize, startingSize); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AccessibilityNodeInfo accessibilityNodeInfo = - mirrorView.createAccessibilityNodeInfo(); - assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( - new AccessibilityAction( - R.id.accessibility_action_decrease_window_width, null)); - } - - @Test - public void windowHeightIsMin_noDecreaseWindowHeightA11yAction() { - int mMinWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final int startingSize = mMinWindowSize; - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingSize, startingSize); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AccessibilityNodeInfo accessibilityNodeInfo = - mirrorView.createAccessibilityNodeInfo(); - assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( - new AccessibilityAction( - R.id.accessibility_action_decrease_window_height, null)); - } - - @Test - public void enableWindowMagnification_hasA11yWindowTitle() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - assertThat(getAccessibilityWindowTitle()).isEqualTo(getContext().getResources().getString( - com.android.internal.R.string.android_system_label)); - } - - @Test - public void enableWindowMagnificationWithScaleLessThanOne_enabled_disabled() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(0.9f, Float.NaN, - Float.NaN); - }); - - assertThat(mWindowMagnificationController.getScale()).isEqualTo(Float.NaN); - } - - @Test - public void enableWindowMagnification_rotationIsChanged_updateRotationValue() { - // the config orientation should not be undefined, since it would cause config.diff - // returning 0 and thus the orientation changed would not be detected - assertThat(mResources.getConfiguration().orientation).isNotEqualTo(ORIENTATION_UNDEFINED); - - final Configuration config = mResources.getConfiguration(); - config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT - : ORIENTATION_LANDSCAPE; - final int newRotation = simulateRotateTheDevice(); - - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, Float.NaN)); - - assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation); - } - - @Test - public void enableWindowMagnification_registerComponentCallback() { - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, - Float.NaN)); - - verify(mContext).registerComponentCallbacks(mWindowMagnificationController); - } - - @Test - public void onLocaleChanged_enabled_updateA11yWindowTitle() { - final String newA11yWindowTitle = "new a11y window title"; - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - final TestableResources testableResources = getContext().getOrCreateTestableResources(); - testableResources.addOverride(com.android.internal.R.string.android_system_label, - newA11yWindowTitle); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE); - }); - - assertThat(getAccessibilityWindowTitle()).isEqualTo(newA11yWindowTitle); - } - - @Ignore("it's flaky in presubmit but works in abtd, filter for now. b/305654925") - @Test - public void onSingleTap_enabled_scaleAnimates() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onSingleTap(mSpyView); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - - final AtomicDouble maxScaleX = new AtomicDouble(); - advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> { - // For some reason the fancy way doesn't compile... - // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max); - final double oldMax = maxScaleX.get(); - final double newMax = Math.max(mirrorView.getScaleX(), oldMax); - assertThat(maxScaleX.compareAndSet(oldMax, newMax)).isTrue(); - }); - - assertThat(maxScaleX.get()).isGreaterThan(1.0); - } - - @Test - public void moveWindowMagnificationToTheBottom_enabledWithGestureInset_overlapFlagIsTrue() { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - setSystemGestureInsets(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.moveWindowMagnifier(0, bounds.height()); - }); - - ReferenceTestUtils.waitForCondition(() -> hasMagnificationOverlapFlag()); - } - - @Test - public void moveWindowMagnificationToRightEdge_dragHandleMovesToLeftAndUpdatesTapExcludeRegion() - throws RemoteException { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - setSystemGestureInsets(); - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.updateWindowMagnificationInternal( - Float.NaN, Float.NaN, Float.NaN); - }); - // Wait for Region updated. - waitForIdleSync(); - - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.moveWindowMagnifier(bounds.width(), 0); - }); - // Wait for Region updated. - waitForIdleSync(); - - AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl(); - // Verifying two times in: (1) enable window magnification (2) reposition drag handle - verify(viewRoot, times(2)).setTouchableRegion(any()); - - View dragButton = getInternalView(R.id.drag_handle); - FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams(); - assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.LEFT); - } - - @Test - public void moveWindowMagnificationToLeftEdge_dragHandleMovesToRightAndUpdatesTapExcludeRegion() - throws RemoteException { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - setSystemGestureInsets(); - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.updateWindowMagnificationInternal( - Float.NaN, Float.NaN, Float.NaN); - }); - // Wait for Region updated. - waitForIdleSync(); - - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.moveWindowMagnifier(-bounds.width(), 0); - }); - // Wait for Region updated. - waitForIdleSync(); - - AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl(); - // Verifying one times in: (1) enable window magnification - verify(viewRoot).setTouchableRegion(any()); - - View dragButton = getInternalView(R.id.drag_handle); - FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams(); - assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.RIGHT); - } - - @Test - public void setMinimumWindowSize_enabled_expectedWindowSize() { - final int minimumWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final int expectedWindowHeight = minimumWindowSize; - final int expectedWindowWidth = minimumWindowSize; - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, Float.NaN)); - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight); - actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); - - }); - - assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight); - assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth); - } - - @Test - public void setMinimumWindowSizeThenEnable_expectedWindowSize() { - final int minimumWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final int expectedWindowHeight = minimumWindowSize; - final int expectedWindowWidth = minimumWindowSize; - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight); - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, Float.NaN); - actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight); - assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth); - } - - @Test - public void setWindowSizeLessThanMin_enabled_minimumWindowSize() { - final int minimumWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, Float.NaN)); - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.setWindowSize(minimumWindowSize - 10, - minimumWindowSize - 10); - actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - assertThat(actualWindowHeight.get()).isEqualTo(minimumWindowSize); - assertThat(actualWindowWidth.get()).isEqualTo(minimumWindowSize); - } - - @Test - public void setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize() { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, Float.NaN)); - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.setWindowSize(bounds.width() + 10, bounds.height() + 10); - actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - assertThat(actualWindowHeight.get()).isEqualTo(bounds.height()); - assertThat(actualWindowWidth.get()).isEqualTo(bounds.width()); - } - - @Test - public void changeMagnificationSize_expectedWindowSize() { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - - final float magnificationScaleLarge = 2.5f; - final int initSize = Math.min(bounds.width(), bounds.height()) / 3; - final int magnificationSize = (int) (initSize * magnificationScaleLarge) - - (int) (initSize * magnificationScaleLarge) % 2; - - final int expectedWindowHeight = magnificationSize; - final int expectedWindowWidth = magnificationSize; - - mInstrumentation.runOnMainSync( - () -> - mWindowMagnificationController.updateWindowMagnificationInternal( - Float.NaN, Float.NaN, Float.NaN)); - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.changeMagnificationSize( - WindowMagnificationSettings.MagnificationSize.LARGE); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight); - assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth); - } - - @Test - public void editModeOnDragCorner_resizesWindow() { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - - final int startingSize = (int) (bounds.width() / 2); - - mInstrumentation.runOnMainSync( - () -> - mWindowMagnificationController.updateWindowMagnificationInternal( - Float.NaN, Float.NaN, Float.NaN)); - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.setWindowSize(startingSize, startingSize); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - waitForIdleSync(); - - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController - .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1); - assertThat(actualWindowWidth.get()).isEqualTo(startingSize + 2); - } - - @Test - public void editModeOnDragEdge_resizesWindowInOnlyOneDirection() { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - - final int startingSize = (int) (bounds.width() / 2f); - - mInstrumentation.runOnMainSync( - () -> - mWindowMagnificationController.updateWindowMagnificationInternal( - Float.NaN, Float.NaN, Float.NaN)); - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.setWindowSize(startingSize, startingSize); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - mWindowMagnificationController - .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1); - assertThat(actualWindowWidth.get()).isEqualTo(startingSize); - } - - @Test - public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() { - - final int minimumWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, Float.NaN)); - - final AtomicInteger magnificationCenterX = new AtomicInteger(); - final AtomicInteger magnificationCenterY = new AtomicInteger(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.setWindowSizeAndCenter(minimumWindowSize, - minimumWindowSize, bounds.right, bounds.bottom); - magnificationCenterX.set((int) mWindowMagnificationController.getCenterX()); - magnificationCenterY.set((int) mWindowMagnificationController.getCenterY()); - }); - - assertThat(magnificationCenterX.get()).isLessThan(bounds.right); - assertThat(magnificationCenterY.get()).isLessThan(bounds.bottom); - } - - @Test - public void performSingleTap_DragHandle() { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.updateWindowMagnificationInternal( - 1.5f, bounds.centerX(), bounds.centerY()); - }); - View dragButton = getInternalView(R.id.drag_handle); - - // Perform a single-tap - final long downTime = SystemClock.uptimeMillis(); - dragButton.dispatchTouchEvent( - obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100)); - dragButton.dispatchTouchEvent( - obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100)); - - verify(mSurfaceControlViewHost).setView(any(View.class), any()); - } - - private <T extends View> T getInternalView(@IdRes int idRes) { - View mirrorView = mSurfaceControlViewHost.getView(); - T view = mirrorView.findViewById(idRes); - assertThat(view).isNotNull(); - return view; - } - - private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x, - float y) { - return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y); - } - - private String getAccessibilityWindowTitle() { - final View mirrorView = mSurfaceControlViewHost.getView(); - if (mirrorView == null) { - return null; - } - WindowManager.LayoutParams layoutParams = - (WindowManager.LayoutParams) mirrorView.getLayoutParams(); - return layoutParams.accessibilityTitle.toString(); - } - - private boolean hasMagnificationOverlapFlag() { - return (mSysUiState.getFlags() & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0; - } - - private void setSystemGestureInsets() { - final WindowInsets testInsets = new WindowInsets.Builder() - .setInsets(systemGestures(), Insets.of(0, 0, 0, 10)) - .build(); - mWindowManager.setWindowInsets(testInsets); - } - - private int updateMirrorSurfaceMarginDimension() { - return mContext.getResources().getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - } - - @Surface.Rotation - private int simulateRotateTheDevice() { - final Display display = Mockito.spy(mContext.getDisplay()); - final int currentRotation = display.getRotation(); - final int newRotation = (currentRotation + 1) % 4; - when(display.getRotation()).thenReturn(newRotation); - when(mContext.getDisplay()).thenReturn(display); - return newRotation; - } - - // advance time based on the device frame refresh rate - private void advanceTimeBy(long timeDelta) { - advanceTimeBy(timeDelta, /* runnableOnEachRefresh= */ null); - } - - // advance time based on the device frame refresh rate, and trigger runnable on each refresh - private void advanceTimeBy(long timeDelta, @Nullable Runnable runnableOnEachRefresh) { - final float frameRate = mContext.getDisplay().getRefreshRate(); - final int timeSlot = (int) (1000 / frameRate); - int round = (int) Math.ceil((double) timeDelta / timeSlot); - for (; round >= 0; round--) { - mInstrumentation.runOnMainSync(() -> { - mAnimatorTestRule.advanceTimeBy(timeSlot); - if (runnableOnEachRefresh != null) { - runnableOnEachRefresh.run(); - } - }); - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt index 288ed4dc9d4f..a1f59c2cc2b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt @@ -20,18 +20,20 @@ import android.animation.AnimatorRuleRecordingSpec import android.animation.AnimatorTestRuleToolkit import android.animation.MotionControl import android.animation.recordMotion +import android.graphics.Color +import android.graphics.PointF import android.graphics.drawable.GradientDrawable import android.platform.test.annotations.MotionTest import android.view.ViewGroup import android.widget.FrameLayout import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.filters.SmallTest -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.systemui.SysuiTestCase import com.android.systemui.activity.EmptyTestActivity import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope +import com.android.systemui.runOnMainThreadAndWaitForIdleSync import kotlin.test.assertTrue import org.junit.Rule import org.junit.Test @@ -47,13 +49,25 @@ import platform.test.screenshot.PathConfig @SmallTest @MotionTest @RunWith(ParameterizedAndroidJunit4::class) -class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() { +class TransitionAnimatorTest( + private val fadeWindowBackgroundLayer: Boolean, + private val isLaunching: Boolean, + private val useSpring: Boolean, +) : SysuiTestCase() { companion object { private const val GOLDENS_PATH = "frameworks/base/packages/SystemUI/tests/goldens" - @get:Parameters(name = "{0}") + @get:Parameters(name = "fadeBackground={0}, isLaunching={1}, useSpring={2}") @JvmStatic - val useSpringValues = booleanArrayOf(false, true).toList() + val parameterValues = buildList { + booleanArrayOf(true, false).forEach { fadeBackground -> + booleanArrayOf(true, false).forEach { isLaunching -> + booleanArrayOf(true, false).forEach { useSpring -> + add(arrayOf(fadeBackground, isLaunching, useSpring)) + } + } + } + } } private val kosmos = Kosmos() @@ -66,11 +80,23 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() { ActivityTransitionAnimator.SPRING_TIMINGS, ActivityTransitionAnimator.SPRING_INTERPOLATORS, ) - private val withSpring = + private val fade = + if (fadeWindowBackgroundLayer) { + "withFade" + } else { + "withoutFade" + } + private val direction = + if (isLaunching) { + "whenLaunching" + } else { + "whenReturning" + } + private val mode = if (useSpring) { - "_withSpring" + "withSpring" } else { - "" + "withAnimator" } @get:Rule(order = 1) val activityRule = ActivityScenarioRule(EmptyTestActivity::class.java) @@ -83,113 +109,75 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() { ) @Test - fun backgroundAnimation_whenLaunching() { - val backgroundLayer = GradientDrawable().apply { alpha = 0 } - val animator = - setUpTest(backgroundLayer, isLaunching = true).apply { - getInstrumentation().runOnMainSync { start() } - } + fun backgroundAnimationTimeSeries() { + val transitionContainer = createScene() + val backgroundLayer = createBackgroundLayer() + val animation = createAnimation(transitionContainer, backgroundLayer) - val recordedMotion = recordMotion(backgroundLayer, animator) + val recordedMotion = record(backgroundLayer, animation) motionRule .assertThat(recordedMotion) - .timeSeriesMatchesGolden("backgroundAnimation_whenLaunching$withSpring") + .timeSeriesMatchesGolden("backgroundAnimationTimeSeries_${fade}_${direction}_$mode") } - @Test - fun backgroundAnimation_whenReturning() { - val backgroundLayer = GradientDrawable().apply { alpha = 0 } - val animator = - setUpTest(backgroundLayer, isLaunching = false).apply { - getInstrumentation().runOnMainSync { start() } - } - - val recordedMotion = recordMotion(backgroundLayer, animator) - - motionRule - .assertThat(recordedMotion) - .timeSeriesMatchesGolden("backgroundAnimation_whenReturning$withSpring") - } - - @Test - fun backgroundAnimationWithoutFade_whenLaunching() { - val backgroundLayer = GradientDrawable().apply { alpha = 0 } - val animator = - setUpTest(backgroundLayer, isLaunching = true, fadeWindowBackgroundLayer = false) - .apply { getInstrumentation().runOnMainSync { start() } } - - val recordedMotion = recordMotion(backgroundLayer, animator) - - motionRule - .assertThat(recordedMotion) - .timeSeriesMatchesGolden("backgroundAnimationWithoutFade_whenLaunching$withSpring") - } - - @Test - fun backgroundAnimationWithoutFade_whenReturning() { - val backgroundLayer = GradientDrawable().apply { alpha = 0 } - val animator = - setUpTest(backgroundLayer, isLaunching = false, fadeWindowBackgroundLayer = false) - .apply { getInstrumentation().runOnMainSync { start() } } - - val recordedMotion = recordMotion(backgroundLayer, animator) - - motionRule - .assertThat(recordedMotion) - .timeSeriesMatchesGolden("backgroundAnimationWithoutFade_whenReturning$withSpring") - } - - private fun setUpTest( - backgroundLayer: GradientDrawable, - isLaunching: Boolean, - fadeWindowBackgroundLayer: Boolean = true, - ): TransitionAnimator.Animation { + private fun createScene(): ViewGroup { lateinit var transitionContainer: ViewGroup activityRule.scenario.onActivity { activity -> - transitionContainer = FrameLayout(activity).apply { setBackgroundColor(0x00FF00) } + transitionContainer = FrameLayout(activity) activity.setContentView(transitionContainer) } waitForIdleSync() + return transitionContainer + } + private fun createBackgroundLayer() = + GradientDrawable().apply { + setColor(Color.BLACK) + alpha = 0 + } + + private fun createAnimation( + transitionContainer: ViewGroup, + backgroundLayer: GradientDrawable, + ): TransitionAnimator.Animation { val controller = TestController(transitionContainer, isLaunching) - return transitionAnimator.createAnimation( - controller, - controller.createAnimatorState(), - createEndState(transitionContainer), - backgroundLayer, - fadeWindowBackgroundLayer, - useSpring = useSpring, - ) - } - private fun createEndState(container: ViewGroup): TransitionAnimator.State { val containerLocation = IntArray(2) - container.getLocationOnScreen(containerLocation) - return TransitionAnimator.State( - left = containerLocation[0], - top = containerLocation[1], - right = containerLocation[0] + 320, - bottom = containerLocation[1] + 690, - topCornerRadius = 0f, - bottomCornerRadius = 0f, - ) + transitionContainer.getLocationOnScreen(containerLocation) + val endState = + TransitionAnimator.State( + left = containerLocation[0], + top = containerLocation[1], + right = containerLocation[0] + 320, + bottom = containerLocation[1] + 690, + topCornerRadius = 0f, + bottomCornerRadius = 0f, + ) + + val startVelocity = + if (useSpring) { + PointF(2500f, 30000f) + } else { + null + } + + return transitionAnimator + .createAnimation( + controller, + controller.createAnimatorState(), + endState, + backgroundLayer, + fadeWindowBackgroundLayer, + startVelocity = startVelocity, + ) + .apply { runOnMainThreadAndWaitForIdleSync { start() } } } - private fun recordMotion( + private fun record( backgroundLayer: GradientDrawable, animation: TransitionAnimator.Animation, ): RecordedMotion { - fun record(motionControl: MotionControl, sampleIntervalMs: Long): RecordedMotion { - return motionRule.recordMotion( - AnimatorRuleRecordingSpec(backgroundLayer, motionControl, sampleIntervalMs) { - feature(DrawableFeatureCaptures.bounds, "bounds") - feature(DrawableFeatureCaptures.cornerRadii, "corner_radii") - feature(DrawableFeatureCaptures.alpha, "alpha") - } - ) - } - val motionControl: MotionControl val sampleIntervalMs: Long if (useSpring) { @@ -204,9 +192,13 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() { sampleIntervalMs = 20L } - var recording: RecordedMotion? = null - getInstrumentation().runOnMainSync { recording = record(motionControl, sampleIntervalMs) } - return recording!! + return motionRule.recordMotion( + AnimatorRuleRecordingSpec(backgroundLayer, motionControl, sampleIntervalMs) { + feature(DrawableFeatureCaptures.bounds, "bounds") + feature(DrawableFeatureCaptures.cornerRadii, "corner_radii") + feature(DrawableFeatureCaptures.alpha, "alpha") + } + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index 6061063db903..562481567536 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -20,6 +20,7 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS; import static com.android.systemui.Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE; +import static com.android.systemui.Flags.FLAG_SHOW_CLIPBOARD_INDICATION; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED; @@ -121,6 +122,24 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + private class FakeClipboardIndicationProvider implements ClipboardIndicationProvider { + private ClipboardIndicationCallback mIndicationCallback; + + public void notifyIndicationTextChanged(CharSequence indicationText) { + if (mIndicationCallback != null) { + mIndicationCallback.onIndicationTextChanged(indicationText); + } + } + + @Override + public void getIndicationText(ClipboardIndicationCallback callback) { + mIndicationCallback = callback; + } + } + + private FakeClipboardIndicationProvider mClipboardIndicationProvider = + new FakeClipboardIndicationProvider(); + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -156,6 +175,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { mExecutor, mClipboardImageLoader, mClipboardTransitionExecutor, + mClipboardIndicationProvider, mUiEventLogger); verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture()); mCallbacks = mOverlayCallbacksCaptor.getValue(); @@ -305,6 +325,17 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_SHOW_CLIPBOARD_INDICATION) + public void test_onIndicationTextChanged_setIndicationTextCorrectly() { + initController(); + mOverlayController.setClipData(mSampleClipData, ""); + + mClipboardIndicationProvider.notifyIndicationTextChanged("copied"); + + verify(mClipboardOverlayView).setIndicationText("copied"); + } + + @Test @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS) public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() { initController(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt index 4228c3c0b110..7e6a7271c561 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt @@ -32,7 +32,6 @@ import com.android.systemui.haptics.vibratorHelper import com.android.systemui.keyguard.dismissCallbackRegistry import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor -import com.android.systemui.keyguard.domain.interactor.windowManagerLockscreenVisibilityInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testScope @@ -78,7 +77,6 @@ val Kosmos.sceneContainerStartable by Fixture { uiEventLogger = uiEventLogger, sceneBackInteractor = sceneBackInteractor, shadeSessionStorage = shadeSessionStorage, - windowMgrLockscreenVisInteractor = windowManagerLockscreenVisibilityInteractor, keyguardEnabledInteractor = keyguardEnabledInteractor, dismissCallbackRegistry = dismissCallbackRegistry, statusBarStateController = sysuiStatusBarStateController, diff --git a/packages/overlays/Android.bp b/packages/overlays/Android.bp index 5075f6322d1b..44abb9ffb34f 100644 --- a/packages/overlays/Android.bp +++ b/packages/overlays/Android.bp @@ -21,6 +21,7 @@ package { phony { name: "frameworks-base-overlays", + product_specific: true, required: [ "DisplayCutoutEmulationCornerOverlay", "DisplayCutoutEmulationDoubleOverlay", diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index b3f78ab30021..4731cfbfc935 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -279,6 +279,15 @@ cc_defaults { shared_libs: [ "liblog", ], + visibility: ["//visibility:private"], +} + +cc_library_host_shared { + name: "libravenwood_initializer", + defaults: ["ravenwood_jni_defaults"], + srcs: [ + "runtime-jni/ravenwood_initializer.cpp", + ], } // We need this as a separate library because we need to overload the @@ -301,7 +310,6 @@ cc_library_host_shared { "libutils", "libcutils", ], - visibility: ["//frameworks/base"], } // For collecting the *stats.csv files in a known directory under out/host/linux-x86/testcases/. @@ -659,6 +667,7 @@ android_ravenwood_libgroup { ], jni_libs: [ // Libraries has to be loaded in the following order + "libravenwood_initializer", "libravenwood_sysprop", "libravenwood_runtime", "libandroid_runtime", diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java index d29b93c0c171..a208d6dce2ce 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java @@ -40,7 +40,7 @@ public final class RavenwoodNativeLoader { * See frameworks/base/core/jni/platform/host/HostRuntime.cpp */ private static final Class<?>[] sLibandroidClasses = { - android.util.Log.class, +// android.util.Log.class, // Not using native log: b/377377826 android.os.Parcel.class, android.os.Binder.class, android.os.SystemProperties.class, diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java index 28c262d53ff1..91778579ab28 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -51,6 +51,7 @@ import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; import com.android.hoststubgen.hosthelper.HostTestUtils; +import com.android.internal.annotations.GuardedBy; import com.android.internal.os.RuntimeInit; import com.android.ravenwood.RavenwoodRuntimeNative; import com.android.ravenwood.RavenwoodRuntimeState; @@ -86,6 +87,7 @@ public class RavenwoodRuntimeEnvironmentController { } private static final String MAIN_THREAD_NAME = "RavenwoodMain"; + private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer"; private static final String RAVENWOOD_NATIVE_SYSPROP_NAME = "ravenwood_sysprop"; private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime"; private static final String RAVENWOOD_BUILD_PROP = @@ -139,23 +141,61 @@ public class RavenwoodRuntimeEnvironmentController { return res; } + private static final Object sInitializationLock = new Object(); + + @GuardedBy("sInitializationLock") + private static boolean sInitialized = false; + + @GuardedBy("sInitializationLock") + private static Throwable sExceptionFromGlobalInit; + private static RavenwoodAwareTestRunner sRunner; private static RavenwoodSystemProperties sProps; - private static boolean sInitialized = false; /** * Initialize the global environment. */ public static void globalInitOnce() { - if (sInitialized) { - return; + synchronized (sInitializationLock) { + if (!sInitialized) { + // globalInitOnce() is called from class initializer, which cause + // this method to be called recursively, + sInitialized = true; + + // This is the first call. + try { + globalInitInner(); + } catch (Throwable th) { + Log.e(TAG, "globalInit() failed", th); + + sExceptionFromGlobalInit = th; + throw th; + } + } else { + // Subsequent calls. If the first call threw, just throw the same error, to prevent + // the test from running. + if (sExceptionFromGlobalInit != null) { + Log.e(TAG, "globalInit() failed re-throwing the same exception", + sExceptionFromGlobalInit); + + SneakyThrow.sneakyThrow(sExceptionFromGlobalInit); + } + } + } + } + + private static void globalInitInner() { + if (RAVENWOOD_VERBOSE_LOGGING) { + Log.v(TAG, "globalInit() called here...", new RuntimeException("NOT A CRASH")); } - sInitialized = true; + + // Some process-wide initialization. (maybe redirect stdout/stderr) + RavenwoodCommonUtils.loadJniLibrary(LIBRAVENWOOD_INITIALIZER_NAME); // We haven't initialized liblog yet, so directly write to System.out here. - RavenwoodCommonUtils.log(TAG, "globalInit()"); + RavenwoodCommonUtils.log(TAG, "globalInitInner()"); - // Load libravenwood_sysprop first + // Load libravenwood_sysprop before other libraries that may use SystemProperties. var libProp = RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_SYSPROP_NAME); System.load(libProp); RavenwoodRuntimeNative.reloadNativeLibrary(libProp); diff --git a/ravenwood/runtime-jni/ravenwood_initializer.cpp b/ravenwood/runtime-jni/ravenwood_initializer.cpp new file mode 100644 index 000000000000..89fb7c3c3510 --- /dev/null +++ b/ravenwood/runtime-jni/ravenwood_initializer.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + /* + * This file is compiled into a single SO file, which we load at the very first. + * We can do process-wide initialization here. + */ + +#include <fcntl.h> +#include <unistd.h> + +#include "jni_helper.h" + +static void maybeRedirectLog() { + auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT"); + if (ravenwoodLogOut == NULL) { + return; + } + ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to %s", ravenwoodLogOut); + + // Redirect stdin / stdout to /dev/tty. + int ttyFd = open(ravenwoodLogOut, O_WRONLY | O_APPEND); + if (ttyFd == -1) { + ALOGW("$RAVENWOOD_LOG_OUT is set to %s, but failed to open: %s ", ravenwoodLogOut, + strerror(errno)); + return; + } + dup2(ttyFd, 1); + dup2(ttyFd, 2); +} + +extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { + ALOGI("%s: JNI_OnLoad", __FILE__); + + maybeRedirectLog(); + return JNI_VERSION_1_4; +} diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp index 5b75e9854758..c1993f691686 100644 --- a/ravenwood/runtime-jni/ravenwood_runtime.cpp +++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp @@ -180,24 +180,6 @@ static jint Linux_gettid(JNIEnv* env, jobject) { return syscall(__NR_gettid); } -static void maybeRedirectLog() { - auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT"); - if (ravenwoodLogOut == NULL) { - return; - } - ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to %s", ravenwoodLogOut); - - // Redirect stdin / stdout to /dev/tty. - int ttyFd = open(ravenwoodLogOut, O_WRONLY); - if (ttyFd == -1) { - ALOGW("$RAVENWOOD_LOG_OUT is set to %s, but failed to open: %s ", ravenwoodLogOut, - strerror(errno)); - return; - } - dup2(ttyFd, 1); - dup2(ttyFd, 2); -} - // ---- Registration ---- extern void register_android_system_OsConstants(JNIEnv* env); @@ -218,8 +200,6 @@ static const JNINativeMethod sMethods[] = }; extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { - maybeRedirectLog(); - ALOGI("%s: JNI_OnLoad", __FILE__); JNIEnv* env = GetJNIEnvOrDie(vm); diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh index 672c685aa6d7..1910100a7f5d 100755 --- a/ravenwood/scripts/run-ravenwood-tests.sh +++ b/ravenwood/scripts/run-ravenwood-tests.sh @@ -26,7 +26,7 @@ # Regex to identify slow tests, in PCRE -SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood)$' +SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood|CarSystemUIRavenTests)$' smoke=0 include_re="" @@ -67,7 +67,7 @@ filter() { if [[ "$re" == "" ]] ; then cat # No filtering else - grep $grep_arg -P "$re" + grep $grep_arg -iP "$re" fi } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 51034d24df14..7cba9e0ccca8 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -31,16 +31,13 @@ import static android.os.UserHandle.getCallingUserId; import static com.android.internal.util.CollectionUtils.any; import static com.android.internal.util.Preconditions.checkState; -import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature; -import static com.android.server.companion.utils.PackageUtils.getPackageInfo; import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId; import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.MINUTES; import android.annotation.EnforcePermission; import android.annotation.NonNull; @@ -69,31 +66,22 @@ import android.companion.datatransfer.PermissionSyncRequest; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.net.MacAddress; -import android.net.NetworkPolicyManager; import android.os.Binder; -import android.os.Environment; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.PowerExemptionManager; import android.os.PowerManagerInternal; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.permission.flags.Flags; -import android.util.ArraySet; import android.util.ExceptionUtils; import android.util.Slog; -import com.android.internal.app.IAppOpsService; import com.android.internal.content.PackageMonitor; import com.android.internal.notification.NotificationAccessConfirmationActivityContract; -import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.server.FgThread; @@ -114,35 +102,25 @@ import com.android.server.companion.devicepresence.DevicePresenceProcessor; import com.android.server.companion.devicepresence.ObservableUuid; import com.android.server.companion.devicepresence.ObservableUuidStore; import com.android.server.companion.transport.CompanionTransportManager; -import com.android.server.pm.UserManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; -import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collection; import java.util.List; -import java.util.Set; @SuppressLint("LongLogTag") public class CompanionDeviceManagerService extends SystemService { private static final String TAG = "CDM_CompanionDeviceManagerService"; private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min - - private static final String PREF_FILE_NAME = "companion_device_preferences.xml"; - private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done"; private static final int MAX_CN_LENGTH = 500; - private final ActivityTaskManagerInternal mAtmInternal; - private final ActivityManagerInternal mAmInternal; - private final IAppOpsService mAppOpsManager; - private final PowerExemptionManager mPowerExemptionManager; - private final PackageManagerInternal mPackageManagerInternal; - private final AssociationStore mAssociationStore; private final SystemDataTransferRequestStore mSystemDataTransferRequestStore; private final ObservableUuidStore mObservableUuidStore; + + private final CompanionExemptionProcessor mCompanionExemptionProcessor; private final AssociationRequestsProcessor mAssociationRequestsProcessor; private final SystemDataTransferProcessor mSystemDataTransferProcessor; private final BackupRestoreProcessor mBackupRestoreProcessor; @@ -156,12 +134,15 @@ public class CompanionDeviceManagerService extends SystemService { super(context); final ActivityManager activityManager = context.getSystemService(ActivityManager.class); - mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class); - mAppOpsManager = IAppOpsService.Stub.asInterface( - ServiceManager.getService(Context.APP_OPS_SERVICE)); - mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); - mAmInternal = LocalServices.getService(ActivityManagerInternal.class); - mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + final PowerExemptionManager powerExemptionManager = context.getSystemService( + PowerExemptionManager.class); + final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + final ActivityTaskManagerInternal atmInternal = LocalServices.getService( + ActivityTaskManagerInternal.class); + final ActivityManagerInternal amInternal = LocalServices.getService( + ActivityManagerInternal.class); + final PackageManagerInternal packageManagerInternal = LocalServices.getService( + PackageManagerInternal.class); final UserManager userManager = context.getSystemService(UserManager.class); final PowerManagerInternal powerManagerInternal = LocalServices.getService( PowerManagerInternal.class); @@ -173,25 +154,29 @@ public class CompanionDeviceManagerService extends SystemService { // Init processors mAssociationRequestsProcessor = new AssociationRequestsProcessor(context, - mPackageManagerInternal, mAssociationStore); - mBackupRestoreProcessor = new BackupRestoreProcessor(context, mPackageManagerInternal, + packageManagerInternal, mAssociationStore); + mBackupRestoreProcessor = new BackupRestoreProcessor(context, packageManagerInternal, mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore, mAssociationRequestsProcessor); mCompanionAppBinder = new CompanionAppBinder(context); + mCompanionExemptionProcessor = new CompanionExemptionProcessor(context, + powerExemptionManager, appOpsManager, packageManagerInternal, atmInternal, + amInternal, mAssociationStore); + mDevicePresenceProcessor = new DevicePresenceProcessor(context, mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore, - powerManagerInternal); + powerManagerInternal, mCompanionExemptionProcessor); mTransportManager = new CompanionTransportManager(context, mAssociationStore); mDisassociationProcessor = new DisassociationProcessor(context, activityManager, - mAssociationStore, mPackageManagerInternal, mDevicePresenceProcessor, + mAssociationStore, packageManagerInternal, mDevicePresenceProcessor, mCompanionAppBinder, mSystemDataTransferRequestStore, mTransportManager); mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, - mPackageManagerInternal, mAssociationStore, + packageManagerInternal, mAssociationStore, mSystemDataTransferRequestStore, mTransportManager); // TODO(b/279663946): move context sync to a dedicated system service @@ -202,7 +187,6 @@ public class CompanionDeviceManagerService extends SystemService { public void onStart() { // Init association stores mAssociationStore.refreshCache(); - mAssociationStore.registerLocalListener(mAssociationStoreChangeListener); // Init UUID store mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId()); @@ -240,11 +224,8 @@ public class CompanionDeviceManagerService extends SystemService { if (associations.isEmpty()) return; - updateAtm(userId, associations); - - BackgroundThread.getHandler().sendMessageDelayed( - obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this), - MINUTES.toMillis(10)); + mCompanionExemptionProcessor.updateAtm(userId, associations); + mCompanionExemptionProcessor.updateAutoRevokeExemptions(); } @Override @@ -262,9 +243,12 @@ public class CompanionDeviceManagerService extends SystemService { if (!associationsForPackage.isEmpty()) { Slog.i(TAG, "Package removed or data cleared for user=[" + userId + "], package=[" + packageName + "]. Cleaning up CDM data..."); - } - for (AssociationInfo association : associationsForPackage) { - mDisassociationProcessor.disassociate(association.getId()); + + for (AssociationInfo association : associationsForPackage) { + mDisassociationProcessor.disassociate(association.getId()); + } + + mCompanionAppBinder.onPackageChanged(userId); } // Clear observable UUIDs for the package. @@ -273,19 +257,16 @@ public class CompanionDeviceManagerService extends SystemService { for (ObservableUuid uuid : uuidsTobeObserved) { mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName); } - - mCompanionAppBinder.onPackagesChanged(userId); } private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) { - final List<AssociationInfo> associationsForPackage = + final List<AssociationInfo> associations = mAssociationStore.getAssociationsByPackage(userId, packageName); - for (AssociationInfo association : associationsForPackage) { - updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(), - association.getPackageName()); - } + if (!associations.isEmpty()) { + mCompanionExemptionProcessor.exemptPackage(userId, packageName, false); - mCompanionAppBinder.onPackagesChanged(userId); + mCompanionAppBinder.onPackageChanged(userId); + } } private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) { @@ -765,130 +746,6 @@ public class CompanionDeviceManagerService extends SystemService { } } - /** - * Update special access for the association's package - */ - public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) { - final PackageInfo packageInfo = - getPackageInfo(getContext(), userId, packageName); - - Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo)); - } - - private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) { - if (packageInfo == null) { - return; - } - - if (containsEither(packageInfo.requestedPermissions, - android.Manifest.permission.RUN_IN_BACKGROUND, - android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) { - mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName); - } else { - try { - mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName); - } catch (UnsupportedOperationException e) { - Slog.w(TAG, packageInfo.packageName + " can't be removed from power save" - + " whitelist. It might due to the package is whitelisted by the system."); - } - } - - NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext()); - try { - if (containsEither(packageInfo.requestedPermissions, - android.Manifest.permission.USE_DATA_IN_BACKGROUND, - android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)) { - networkPolicyManager.addUidPolicy( - packageInfo.applicationInfo.uid, - NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND); - } else { - networkPolicyManager.removeUidPolicy( - packageInfo.applicationInfo.uid, - NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND); - } - } catch (IllegalArgumentException e) { - Slog.e(TAG, e.getMessage()); - } - - exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid); - } - - private void exemptFromAutoRevoke(String packageName, int uid) { - try { - mAppOpsManager.setMode( - AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, - uid, - packageName, - AppOpsManager.MODE_IGNORED); - } catch (RemoteException e) { - Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e); - } - } - - private void updateAtm(int userId, List<AssociationInfo> associations) { - final Set<Integer> companionAppUids = new ArraySet<>(); - for (AssociationInfo association : associations) { - final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(), - 0, userId); - if (uid >= 0) { - companionAppUids.add(uid); - } - } - if (mAtmInternal != null) { - mAtmInternal.setCompanionAppUids(userId, companionAppUids); - } - if (mAmInternal != null) { - // Make a copy of the set and send it to ActivityManager. - mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids)); - } - } - - private void maybeGrantAutoRevokeExemptions() { - Slog.d(TAG, "maybeGrantAutoRevokeExemptions()"); - - PackageManager pm = getContext().getPackageManager(); - for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) { - SharedPreferences pref = getContext().getSharedPreferences( - new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME), - Context.MODE_PRIVATE); - if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) { - continue; - } - - try { - final List<AssociationInfo> associations = - mAssociationStore.getActiveAssociationsByUser(userId); - for (AssociationInfo a : associations) { - try { - int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); - exemptFromAutoRevoke(a.getPackageName(), uid); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e); - } - } - } finally { - pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply(); - } - } - } - - private final AssociationStore.OnChangeListener mAssociationStoreChangeListener = - new AssociationStore.OnChangeListener() { - @Override - public void onAssociationChanged(int changeType, AssociationInfo association) { - Slog.d(TAG, "onAssociationChanged changeType=[" + changeType - + "], association=[" + association); - - final int userId = association.getUserId(); - final List<AssociationInfo> updatedAssociations = - mAssociationStore.getActiveAssociationsByUser(userId); - - updateAtm(userId, updatedAssociations); - updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(), - association.getPackageName()); - } - }; - private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override public void onPackageRemoved(String packageName, int uid) { @@ -911,10 +768,6 @@ public class CompanionDeviceManagerService extends SystemService { } }; - private static <T> boolean containsEither(T[] array, T a, T b) { - return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); - } - private class LocalService implements CompanionDeviceManagerServiceInternal { @Override diff --git a/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java new file mode 100644 index 000000000000..4969ffbfa08a --- /dev/null +++ b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion; + +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_IGNORED; + +import static com.android.server.companion.utils.PackageUtils.getPackageInfo; + +import android.annotation.SuppressLint; +import android.app.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.companion.AssociationInfo; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; +import android.net.NetworkPolicyManager; +import android.os.Binder; +import android.os.Environment; +import android.os.PowerExemptionManager; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.internal.util.ArrayUtils; +import com.android.server.LocalServices; +import com.android.server.companion.association.AssociationStore; +import com.android.server.pm.UserManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; + +import java.io.File; +import java.util.List; +import java.util.Set; + +@SuppressLint("LongLogTag") +public class CompanionExemptionProcessor { + + private static final String TAG = "CDM_CompanionExemptionProcessor"; + + private static final String PREF_FILE_NAME = "companion_device_preferences.xml"; + private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done"; + + private final Context mContext; + private final PowerExemptionManager mPowerExemptionManager; + private final AppOpsManager mAppOpsManager; + private final PackageManagerInternal mPackageManager; + private final ActivityTaskManagerInternal mAtmInternal; + private final ActivityManagerInternal mAmInternal; + private final AssociationStore mAssociationStore; + + public CompanionExemptionProcessor(Context context, PowerExemptionManager powerExemptionManager, + AppOpsManager appOpsManager, PackageManagerInternal packageManager, + ActivityTaskManagerInternal atmInternal, ActivityManagerInternal amInternal, + AssociationStore associationStore) { + mContext = context; + mPowerExemptionManager = powerExemptionManager; + mAppOpsManager = appOpsManager; + mPackageManager = packageManager; + mAtmInternal = atmInternal; + mAmInternal = amInternal; + mAssociationStore = associationStore; + + mAssociationStore.registerLocalListener(new AssociationStore.OnChangeListener() { + @Override + public void onAssociationChanged(int changeType, AssociationInfo association) { + final int userId = association.getUserId(); + final List<AssociationInfo> updatedAssociations = + mAssociationStore.getActiveAssociationsByUser(userId); + + updateAtm(userId, updatedAssociations); + } + }); + } + + /** + * Update ActivityManager and ActivityTaskManager exemptions + */ + public void updateAtm(int userId, List<AssociationInfo> associations) { + final Set<Integer> companionAppUids = new ArraySet<>(); + for (AssociationInfo association : associations) { + int uid = mPackageManager.getPackageUid(association.getPackageName(), 0, userId); + if (uid >= 0) { + companionAppUids.add(uid); + } + } + if (mAtmInternal != null) { + mAtmInternal.setCompanionAppUids(userId, companionAppUids); + } + if (mAmInternal != null) { + // Make a copy of the set and send it to ActivityManager. + mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids)); + } + } + + /** + * Update special access for the association's package + */ + public void exemptPackage(int userId, String packageName, boolean hasPresentDevices) { + Slog.i(TAG, "Exempting package [" + packageName + "]..."); + + final PackageInfo packageInfo = getPackageInfo(mContext, userId, packageName); + + Binder.withCleanCallingIdentity( + () -> exemptPackageAsSystem(userId, packageInfo, hasPresentDevices)); + } + + @SuppressLint("MissingPermission") + private void exemptPackageAsSystem(int userId, PackageInfo packageInfo, + boolean hasPresentDevices) { + if (packageInfo == null) { + return; + } + + // If the app has run-in-bg permission and present devices, add it to power saver allowlist. + if (containsEither(packageInfo.requestedPermissions, + android.Manifest.permission.RUN_IN_BACKGROUND, + android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND) + && hasPresentDevices) { + mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName); + } else { + try { + mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName); + } catch (UnsupportedOperationException e) { + Slog.w(TAG, packageInfo.packageName + " can't be removed from power save" + + " allowlist. It might be due to the package being allowlisted by the" + + " system."); + } + } + + // If the app has run-in-bg permission and present device, allow metered network use. + NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(mContext); + try { + if (containsEither(packageInfo.requestedPermissions, + android.Manifest.permission.USE_DATA_IN_BACKGROUND, + android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND) + && hasPresentDevices) { + networkPolicyManager.addUidPolicy( + packageInfo.applicationInfo.uid, + NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND); + } else { + networkPolicyManager.removeUidPolicy( + packageInfo.applicationInfo.uid, + NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND); + } + } catch (IllegalArgumentException e) { + Slog.e(TAG, e.getMessage()); + } + + updateAutoRevokeExemption(packageInfo.packageName, packageInfo.applicationInfo.uid, + !mAssociationStore.getActiveAssociationsByPackage(userId, + packageInfo.packageName).isEmpty()); + } + + /** + * Update auto revoke exemptions. + * If the app has any association, exempt it from permission auto revoke. + */ + public void updateAutoRevokeExemptions() { + Slog.d(TAG, "maybeGrantAutoRevokeExemptions()"); + + PackageManager pm = mContext.getPackageManager(); + for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) { + SharedPreferences pref = mContext.getSharedPreferences( + new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME), + Context.MODE_PRIVATE); + if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) { + continue; + } + + try { + final List<AssociationInfo> associations = + mAssociationStore.getActiveAssociationsByUser(userId); + for (AssociationInfo a : associations) { + try { + int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); + updateAutoRevokeExemption(a.getPackageName(), uid, true); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e); + } + } + } finally { + pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply(); + } + } + } + + @SuppressLint("MissingPermission") + private void updateAutoRevokeExemption(String packageName, int uid, boolean hasAssociations) { + try { + mAppOpsManager.setMode( + AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, + uid, + packageName, + hasAssociations ? MODE_IGNORED : MODE_ALLOWED); + } catch (Exception e) { + Slog.e(TAG, "Error while granting auto revoke exemption for " + packageName, e); + } + } + + private <T> boolean containsEither(T[] array, T a, T b) { + return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); + } + +} diff --git a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java index 60f46887fa5c..8307da5f7218 100644 --- a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java +++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java @@ -95,7 +95,9 @@ public class CompanionAppBinder { /** * On package changed. */ - public void onPackagesChanged(@UserIdInt int userId) { + public void onPackageChanged(@UserIdInt int userId) { + // Note: To invalidate the user space for simplicity. We could alternatively manage each + // package, but that would easily cause errors if one case is mis-handled. mCompanionServicesRegister.invalidate(userId); } @@ -299,12 +301,14 @@ public class CompanionAppBinder { private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> { @Override - public synchronized @NonNull Map<String, List<ComponentName>> forUser( + @NonNull + public synchronized Map<String, List<ComponentName>> forUser( @UserIdInt int userId) { return super.forUser(userId); } - synchronized @NonNull List<ComponentName> forPackage( + @NonNull + synchronized List<ComponentName> forPackage( @UserIdInt int userId, @NonNull String packageName) { return forUser(userId).getOrDefault(packageName, Collections.emptyList()); } @@ -314,7 +318,8 @@ public class CompanionAppBinder { } @Override - protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) { + @NonNull + protected final Map<String, List<ComponentName>> create(@UserIdInt int userId) { return PackageUtils.getCompanionServicesForUser(mContext, userId); } } diff --git a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java index a374d279af0b..7b4dd7df8be3 100644 --- a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java +++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java @@ -57,6 +57,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.CollectionUtils; +import com.android.server.companion.CompanionExemptionProcessor; import com.android.server.companion.association.AssociationStore; import java.io.PrintWriter; @@ -101,6 +102,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene private final PowerManagerInternal mPowerManagerInternal; @NonNull private final UserManager mUserManager; + @NonNull + private final CompanionExemptionProcessor mCompanionExemptionProcessor; // NOTE: Same association may appear in more than one of the following sets at the same time. // (E.g. self-managed devices that have MAC addresses, could be reported as present by their @@ -111,7 +114,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene @NonNull private final Set<Integer> mNearbyBleDevices = new HashSet<>(); @NonNull - private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>(); + private final Set<Integer> mConnectedSelfManagedDevices = new HashSet<>(); @NonNull private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>(); @NonNull @@ -146,7 +149,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene @NonNull UserManager userManager, @NonNull AssociationStore associationStore, @NonNull ObservableUuidStore observableUuidStore, - @NonNull PowerManagerInternal powerManagerInternal) { + @NonNull PowerManagerInternal powerManagerInternal, + @NonNull CompanionExemptionProcessor companionExemptionProcessor) { mContext = context; mCompanionAppBinder = companionAppBinder; mAssociationStore = associationStore; @@ -156,6 +160,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene mObservableUuidStore, this); mBleDeviceProcessor = new BleDeviceProcessor(associationStore, this); mPowerManagerInternal = powerManagerInternal; + mCompanionExemptionProcessor = companionExemptionProcessor; } /** Initialize {@link DevicePresenceProcessor} */ @@ -404,7 +409,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene * nearby (for "self-managed" associations). */ public boolean isDevicePresent(int associationId) { - return mReportedSelfManagedDevices.contains(associationId) + return mConnectedSelfManagedDevices.contains(associationId) || mConnectedBtDevices.contains(associationId) || mNearbyBleDevices.contains(associationId) || mSimulated.contains(associationId); @@ -451,7 +456,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene * notifyDeviceAppeared()} */ public void onSelfManagedDeviceConnected(int associationId) { - onDevicePresenceEvent(mReportedSelfManagedDevices, + onDevicePresenceEvent(mConnectedSelfManagedDevices, associationId, EVENT_SELF_MANAGED_APPEARED); } @@ -467,7 +472,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene * notifyDeviceDisappeared()} */ public void onSelfManagedDeviceDisconnected(int associationId) { - onDevicePresenceEvent(mReportedSelfManagedDevices, + onDevicePresenceEvent(mConnectedSelfManagedDevices, associationId, EVENT_SELF_MANAGED_DISAPPEARED); } @@ -475,7 +480,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene * Marks a "self-managed" device as disconnected when binderDied. */ public void onSelfManagedDeviceReporterBinderDied(int associationId) { - onDevicePresenceEvent(mReportedSelfManagedDevices, + onDevicePresenceEvent(mConnectedSelfManagedDevices, associationId, EVENT_SELF_MANAGED_DISAPPEARED); } @@ -683,6 +688,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene if (association.shouldBindWhenPresent()) { bindApplicationIfNeeded(userId, packageName, association.isSelfManaged()); + mCompanionExemptionProcessor.exemptPackage(userId, packageName, true); } else { return; } @@ -715,6 +721,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene // Check if there are other devices associated to the app that are present. if (!shouldBindPackage(userId, packageName)) { mCompanionAppBinder.unbindCompanionApp(userId, packageName); + mCompanionExemptionProcessor.exemptPackage(userId, packageName, false); } break; default: @@ -940,7 +947,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene mConnectedBtDevices.remove(id); mNearbyBleDevices.remove(id); - mReportedSelfManagedDevices.remove(id); + mConnectedSelfManagedDevices.remove(id); mSimulated.remove(id); synchronized (mBtDisconnectedDevices) { mBtDisconnectedDevices.remove(id); @@ -1100,7 +1107,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene out.append("Companion Device Present: "); if (mConnectedBtDevices.isEmpty() && mNearbyBleDevices.isEmpty() - && mReportedSelfManagedDevices.isEmpty()) { + && mConnectedSelfManagedDevices.isEmpty()) { out.append("<empty>\n"); return; } else { @@ -1130,11 +1137,11 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene } out.append(" Self-Reported Devices: "); - if (mReportedSelfManagedDevices.isEmpty()) { + if (mConnectedSelfManagedDevices.isEmpty()) { out.append("<empty>\n"); } else { out.append("\n"); - for (int associationId : mReportedSelfManagedDevices) { + for (int associationId : mConnectedSelfManagedDevices) { AssociationInfo a = mAssociationStore.getAssociationById(associationId); out.append(" ").append(a.toShortString()).append('\n'); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3e7bcb81c47f..6a9bc5e5665b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -11337,7 +11337,9 @@ public class ActivityManagerService extends IActivityManager.Stub } pw.println(" mFgsStartTempAllowList:"); final long currentTimeNow = System.currentTimeMillis(); - final long elapsedRealtimeNow = SystemClock.elapsedRealtime(); + final long tempAllowlistCurrentTime = + com.android.server.deviceidle.Flags.useCpuTimeForTempAllowlist() + ? SystemClock.uptimeMillis() : SystemClock.elapsedRealtime(); mFgsStartTempAllowList.forEach((uid, entry) -> { pw.print(" " + UserHandle.formatUid(uid) + ": "); entry.second.dump(pw); @@ -11345,7 +11347,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Convert entry.mExpirationTime, which is an elapsed time since boot, // to a time since epoch (i.e. System.currentTimeMillis()-based time.) final long expirationInCurrentTime = - currentTimeNow - elapsedRealtimeNow + entry.first; + currentTimeNow - tempAllowlistCurrentTime + entry.first; TimeUtils.dumpTimeWithDelta(pw, expirationInCurrentTime, currentTimeNow); pw.println(); }); diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 416c11090515..da5b1fd1c079 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -101,7 +101,7 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.Executor; -public final class CachedAppOptimizer { +public class CachedAppOptimizer { // Flags stored in the DeviceConfig API. @VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction"; diff --git a/services/core/java/com/android/server/am/FgsTempAllowList.java b/services/core/java/com/android/server/am/FgsTempAllowList.java index c28655655765..5569b6db3285 100644 --- a/services/core/java/com/android/server/am/FgsTempAllowList.java +++ b/services/core/java/com/android/server/am/FgsTempAllowList.java @@ -29,7 +29,7 @@ import java.util.function.BiConsumer; /** * List of keys that have expiration time. - * If the expiration time is less than current elapsedRealtime, the key has expired. + * If the expiration time is less than current uptime, the key has expired. * Otherwise it is valid (or allowed). * * <p>This is used for both FGS-BG-start restriction, and FGS-while-in-use permissions check.</p> @@ -42,7 +42,7 @@ public class FgsTempAllowList<E> { private static final int DEFAULT_MAX_SIZE = 100; /** - * The value is Pair type, Pair.first is the expirationTime(an elapsedRealtime), + * The value is Pair type, Pair.first is the expirationTime(in cpu uptime), * Pair.second is the optional information entry about this key. */ private final SparseArray<Pair<Long, E>> mTempAllowList = new SparseArray<>(); @@ -82,7 +82,9 @@ public class FgsTempAllowList<E> { } // The temp allowlist should be a short list with only a few entries in it. // for a very large list, HashMap structure should be used. - final long now = SystemClock.elapsedRealtime(); + final long now = com.android.server.deviceidle.Flags.useCpuTimeForTempAllowlist() + ? SystemClock.uptimeMillis() + : SystemClock.elapsedRealtime(); final int size = mTempAllowList.size(); if (size > mMaxSize) { Slog.w(TAG_AM, "FgsTempAllowList length:" + size + " exceeds maxSize" @@ -112,12 +114,15 @@ public class FgsTempAllowList<E> { final int index = mTempAllowList.indexOfKey(uid); if (index < 0) { return null; - } else if (mTempAllowList.valueAt(index).first < SystemClock.elapsedRealtime()) { + } + final long timeNow = com.android.server.deviceidle.Flags.useCpuTimeForTempAllowlist() + ? SystemClock.uptimeMillis() + : SystemClock.elapsedRealtime(); + if (mTempAllowList.valueAt(index).first < timeNow) { mTempAllowList.removeAt(index); return null; - } else { - return mTempAllowList.valueAt(index); } + return mTempAllowList.valueAt(index); } } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 08632fe09b19..a85f49614005 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -19,6 +19,7 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL; import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL_IMPLICIT; import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL; +import static android.app.ActivityManager.PROCESS_CAPABILITY_CPU_TIME; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; @@ -155,6 +156,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -469,7 +471,6 @@ public class OomAdjuster { } Process.setThreadPriority(tid, priority); } - } // TODO(b/346822474): hook up global state usage. @@ -499,7 +500,8 @@ public class OomAdjuster { } OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids, - ServiceThread adjusterThread, GlobalState globalState, Injector injector) { + ServiceThread adjusterThread, GlobalState globalState, + CachedAppOptimizer cachedAppOptimizer, Injector injector) { mService = service; mGlobalState = globalState; mInjector = injector; @@ -508,7 +510,7 @@ public class OomAdjuster { mActiveUids = activeUids; mConstants = mService.mConstants; - mCachedAppOptimizer = new CachedAppOptimizer(mService); + mCachedAppOptimizer = cachedAppOptimizer; mCacheOomRanker = new CacheOomRanker(service); mLogger = new OomAdjusterDebugLogger(this, mService.mConstants); @@ -2590,6 +2592,7 @@ public class OomAdjuster { } capability |= getDefaultCapability(app, procState); + capability |= getCpuCapability(app, now); // Procstates below BFGS should never have this capability. if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { @@ -2732,8 +2735,12 @@ public class OomAdjuster { if (app.mOptRecord.setShouldNotFreeze(true, dryRun, app.mOptRecord.shouldNotFreezeReason() | client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) { - // Bail out early, as we only care about the return value for a dryrun. - return true; + if (Flags.useCpuTimeCapability()) { + // Do nothing, capability updated check will handle the dryrun output. + } else { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } } } @@ -2744,6 +2751,8 @@ public class OomAdjuster { // we check the final procstate, and remove it if the procsate is below BFGS. capability |= getBfslCapabilityFromClient(client); + capability |= getCpuCapabilityFromClient(client); + if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) { if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { capability |= cstate.getCurCapability(); @@ -2802,9 +2811,14 @@ public class OomAdjuster { app.mOptRecord.shouldNotFreezeReason() | ProcessCachedOptimizerRecord .SHOULD_NOT_FREEZE_REASON_BINDER_ALLOW_OOM_MANAGEMENT, mAdjSeq)) { - // Bail out early, as we only care about the return value for a dryrun. - return true; + if (Flags.useCpuTimeCapability()) { + // Do nothing, capability updated check will handle the dryrun output. + } else { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } } + capability |= PROCESS_CAPABILITY_CPU_TIME; } // Not doing bind OOM management, so treat // this guy more like a started service. @@ -3046,9 +3060,14 @@ public class OomAdjuster { app.mOptRecord.shouldNotFreezeReason() | ProcessCachedOptimizerRecord .SHOULD_NOT_FREEZE_REASON_BIND_WAIVE_PRIORITY, mAdjSeq)) { - // Bail out early, as we only care about the return value for a dryrun. - return true; + if (Flags.useCpuTimeCapability()) { + // Do nothing, capability updated check will handle the dryrun output. + } else { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } } + capability |= PROCESS_CAPABILITY_CPU_TIME; } } if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) { @@ -3101,9 +3120,24 @@ public class OomAdjuster { capability &= ~PROCESS_CAPABILITY_BFSL; } if (!updated) { - updated = adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup - || (capability != prevCapability - && (capability & prevCapability) == prevCapability); + if (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup) { + updated = true; + } + + if (Flags.useCpuTimeCapability()) { + if ((capability != prevCapability) + && ((capability & prevCapability) == prevCapability)) { + updated = true; + } + } else { + // Ignore PROCESS_CAPABILITY_CPU_TIME in capability comparison + final int curFiltered = capability & ~PROCESS_CAPABILITY_CPU_TIME; + final int prevFiltered = prevCapability & ~PROCESS_CAPABILITY_CPU_TIME; + if ((curFiltered != prevFiltered) + && ((curFiltered & prevFiltered) == prevFiltered)) { + updated = true; + } + } } if (dryRun) { @@ -3179,6 +3213,8 @@ public class OomAdjuster { // we check the final procstate, and remove it if the procsate is below BFGS. capability |= getBfslCapabilityFromClient(client); + capability |= getCpuCapabilityFromClient(client); + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { // If the other app is cached for any reason, for purposes here // we are going to consider it empty. @@ -3189,8 +3225,12 @@ public class OomAdjuster { if (app.mOptRecord.setShouldNotFreeze(true, dryRun, app.mOptRecord.shouldNotFreezeReason() | client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) { - // Bail out early, as we only care about the return value for a dryrun. - return true; + if (Flags.useCpuTimeCapability()) { + // Do nothing, capability updated check will handle the dryrun output. + } else { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } } } @@ -3266,10 +3306,25 @@ public class OomAdjuster { capability &= ~PROCESS_CAPABILITY_BFSL; } - if (dryRun && (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup - || (capability != prevCapability - && (capability & prevCapability) == prevCapability))) { - return true; + if (dryRun) { + if (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup) { + return true; + } + + if (Flags.useCpuTimeCapability()) { + if ((capability != prevCapability) + && ((capability & prevCapability) == prevCapability)) { + return true; + } + } else { + // Ignore PROCESS_CAPABILITY_CPU_TIME in capability comparison + final int curFiltered = capability & ~PROCESS_CAPABILITY_CPU_TIME; + final int prevFiltered = prevCapability & ~PROCESS_CAPABILITY_CPU_TIME; + if ((curFiltered != prevFiltered) + && ((curFiltered & prevFiltered) == prevFiltered)) { + return true; + } + } } if (adj < prevRawAdj) { @@ -3321,6 +3376,29 @@ public class OomAdjuster { return baseCapabilities | networkCapabilities; } + private static int getCpuCapability(ProcessRecord app, long nowUptime) { + final UidRecord uidRec = app.getUidRecord(); + if (uidRec != null && uidRec.isCurAllowListed()) { + // Process has user visible activities. + return PROCESS_CAPABILITY_CPU_TIME; + } + if (UserHandle.isCore(app.uid)) { + // Make sure all system components are not frozen. + return PROCESS_CAPABILITY_CPU_TIME; + } + if (app.mState.getCachedHasVisibleActivities()) { + // Process has user visible activities. + return PROCESS_CAPABILITY_CPU_TIME; + } + if (app.mServices.hasUndemotedShortForegroundService(nowUptime)) { + // It running a short fgs, just give it cpu time. + return PROCESS_CAPABILITY_CPU_TIME; + } + // TODO(b/370817323): Populate this method with all of the reasons to keep a process + // unfrozen. + return 0; + } + /** * @return the BFSL capability from a client (of a service binding or provider). */ @@ -3369,6 +3447,15 @@ public class OomAdjuster { } /** + * @return the CPU capability from a client (of a service binding or provider). + */ + private static int getCpuCapabilityFromClient(ProcessRecord client) { + // Just grant CPU capability every time + // TODO(b/370817323): Populate with reasons to not propagate cpu capability across bindings. + return client.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME; + } + + /** * Checks if for the given app and client, there's a cycle that should skip over the client * for now or use partial values to evaluate the effect of the client binding. * @param app @@ -3949,6 +4036,39 @@ public class OomAdjuster { mCacheOomRanker.dump(pw); } + /** + * Return whether or not a process should be frozen. + */ + boolean getFreezePolicy(ProcessRecord proc) { + // Reasons to not freeze: + if (Flags.useCpuTimeCapability()) { + if ((proc.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME) != 0) { + /// App is important enough (see {@link #getCpuCapability}) or bound by something + /// important enough to not be frozen. + return false; + } + } else { + // The CPU capability handling covers all setShouldNotFreeze paths. Must check + // shouldNotFreeze, if the CPU capability is not being used. + if (proc.mOptRecord.shouldNotFreeze()) { + return false; + } + } + + if (proc.mOptRecord.isFreezeExempt()) { + return false; + } + + // Reasons to freeze: + if (proc.mState.getCurAdj() >= FREEZER_CUTOFF_ADJ) { + // Oomscore is in a high enough state, it is safe to freeze. + return true; + } + + // Default, do not freeze a process. + return false; + } + @GuardedBy({"mService", "mProcLock"}) void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason, boolean immediate, int oldOomAdj) { @@ -3963,43 +4083,44 @@ public class OomAdjuster { (state.getCurAdj() >= FREEZER_CUTOFF_ADJ ^ oldOomAdj >= FREEZER_CUTOFF_ADJ) || oldOomAdj == UNKNOWN_ADJ; final boolean shouldNotFreezeChanged = opt.shouldNotFreezeAdjSeq() == mAdjSeq; - if ((oomAdjChanged || shouldNotFreezeChanged) + final boolean hasCpuCapability = + (PROCESS_CAPABILITY_CPU_TIME & app.mState.getCurCapability()) + == PROCESS_CAPABILITY_CPU_TIME; + final boolean usedToHaveCpuCapability = + (PROCESS_CAPABILITY_CPU_TIME & app.mState.getSetCapability()) + == PROCESS_CAPABILITY_CPU_TIME; + final boolean cpuCapabilityChanged = hasCpuCapability != usedToHaveCpuCapability; + if ((oomAdjChanged || shouldNotFreezeChanged || cpuCapabilityChanged) && Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, CachedAppOptimizer.ATRACE_FREEZER_TRACK, "updateAppFreezeStateLSP " + app.processName + + " pid: " + app.getPid() + " isFreezeExempt: " + opt.isFreezeExempt() + " isFrozen: " + opt.isFrozen() + " shouldNotFreeze: " + opt.shouldNotFreeze() + " shouldNotFreezeReason: " + opt.shouldNotFreezeReason() + " curAdj: " + state.getCurAdj() + " oldOomAdj: " + oldOomAdj - + " immediate: " + immediate); + + " immediate: " + immediate + + " cpuCapability: " + hasCpuCapability); } } - if (app.mOptRecord.isFreezeExempt()) { - return; - } - - // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze - if (opt.isFrozen() && opt.shouldNotFreeze()) { - mCachedAppOptimizer.unfreezeAppLSP(app, - CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason)); - return; - } - - // Use current adjustment when freezing, set adjustment when unfreezing. - if (state.getCurAdj() >= FREEZER_CUTOFF_ADJ && !opt.isFrozen() - && !opt.shouldNotFreeze()) { - if (!immediate) { - mCachedAppOptimizer.freezeAppAsyncLSP(app); - } else { + if (getFreezePolicy(app)) { + // This process should be frozen. + if (immediate && !opt.isFrozen()) { + // And it will be frozen immediately. mCachedAppOptimizer.freezeAppAsyncAtEarliestLSP(app); + } else if (!opt.isFrozen() || !opt.isPendingFreeze()) { + mCachedAppOptimizer.freezeAppAsyncLSP(app); + } + } else { + // This process should not be frozen. + if (opt.isFrozen() || opt.isPendingFreeze()) { + mCachedAppOptimizer.unfreezeAppLSP(app, + CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason)); } - } else if (state.getSetAdj() < FREEZER_CUTOFF_ADJ) { - mCachedAppOptimizer.unfreezeAppLSP(app, - CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason)); } } @@ -4023,7 +4144,8 @@ public class OomAdjuster { final int size = processes.size(); for (int i = 0; i < size; i++) { ProcessRecord proc = processes.get(i); - mCachedAppOptimizer.unfreezeTemporarily(proc, reason); + mCachedAppOptimizer.unfreezeTemporarily(proc, + CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(reason)); } processes.clear(); } diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index 8b660559f550..1b7e8f0bd244 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -758,8 +758,9 @@ public class OomAdjusterModernImpl extends OomAdjuster { OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList, ActiveUids activeUids, ServiceThread adjusterThread, GlobalState globalState, - Injector injector) { - super(service, processList, activeUids, adjusterThread, globalState, injector); + CachedAppOptimizer cachedAppOptimizer, Injector injector) { + super(service, processList, activeUids, adjusterThread, globalState, cachedAppOptimizer, + injector); } private final ProcessRecordNodes mProcessRecordProcStateNodes = new ProcessRecordNodes( diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 5cb8b954a2ba..364497491785 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -256,18 +256,24 @@ final class ProcessServiceRecord { } // Now we need to look at all short-FGS within the process and see if all of them are // procstate-timed-out or not. + return !hasUndemotedShortForegroundService(nowUptime); + } + + boolean hasUndemotedShortForegroundService(long nowUptime) { for (int i = mServices.size() - 1; i >= 0; i--) { final ServiceRecord sr = mServices.valueAt(i); if (!sr.isShortFgs() || !sr.hasShortFgsInfo()) { continue; } if (sr.getShortFgsInfo().getProcStateDemoteTime() >= nowUptime) { - return false; + // This short fgs has not timed out yet. + return true; } } - return true; + return false; } + int getReportedForegroundServiceTypes() { return mRepFgServiceTypes; } diff --git a/services/core/java/com/android/server/am/ProcessStateController.java b/services/core/java/com/android/server/am/ProcessStateController.java index 01468c640f6c..57899228e6ad 100644 --- a/services/core/java/com/android/server/am/ProcessStateController.java +++ b/services/core/java/com/android/server/am/ProcessStateController.java @@ -29,6 +29,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.ServiceThread; /** @@ -44,13 +45,14 @@ public class ProcessStateController { private final GlobalState mGlobalState = new GlobalState(); private ProcessStateController(ActivityManagerService ams, ProcessList processList, - ActiveUids activeUids, ServiceThread handlerThread, OomAdjuster.Injector oomAdjInjector, + ActiveUids activeUids, ServiceThread handlerThread, + CachedAppOptimizer cachedAppOptimizer, OomAdjuster.Injector oomAdjInjector, boolean useOomAdjusterModernImpl) { mOomAdjuster = useOomAdjusterModernImpl ? new OomAdjusterModernImpl(ams, processList, activeUids, handlerThread, - mGlobalState, oomAdjInjector) + mGlobalState, cachedAppOptimizer, oomAdjInjector) : new OomAdjuster(ams, processList, activeUids, handlerThread, mGlobalState, - oomAdjInjector); + cachedAppOptimizer, oomAdjInjector); } /** @@ -594,6 +596,7 @@ public class ProcessStateController { private final ActiveUids mActiveUids; private ServiceThread mHandlerThread = null; + private CachedAppOptimizer mCachedAppOptimizer = null; private OomAdjuster.Injector mOomAdjInjector = null; private boolean mUseOomAdjusterModernImpl = false; @@ -610,24 +613,38 @@ public class ProcessStateController { if (mHandlerThread == null) { mHandlerThread = OomAdjuster.createAdjusterThread(); } + if (mCachedAppOptimizer == null) { + mCachedAppOptimizer = new CachedAppOptimizer(mAms); + } if (mOomAdjInjector == null) { mOomAdjInjector = new OomAdjuster.Injector(); } return new ProcessStateController(mAms, mProcessList, mActiveUids, mHandlerThread, - mOomAdjInjector, mUseOomAdjusterModernImpl); + mCachedAppOptimizer, mOomAdjInjector, mUseOomAdjusterModernImpl); } /** * For Testing Purposes. Set what thread OomAdjuster will offload tasks on to. */ + @VisibleForTesting public Builder setHandlerThread(ServiceThread handlerThread) { mHandlerThread = handlerThread; return this; } /** + * For Testing Purposes. Set the CachedAppOptimzer used by OomAdjuster. + */ + @VisibleForTesting + public Builder setCachedAppOptimizer(CachedAppOptimizer cachedAppOptimizer) { + mCachedAppOptimizer = cachedAppOptimizer; + return this; + } + + /** * For Testing Purposes. Set an injector for OomAdjuster. */ + @VisibleForTesting public Builder setOomAdjusterInjector(OomAdjuster.Injector injector) { mOomAdjInjector = injector; return this; diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 7b4d6c7fff82..bde3ff61c239 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -250,4 +250,11 @@ flag { is_fixed_read_only: true description: "Add +X to the prev scores according to their positions in the process LRU list" bug: "359912586" -}
\ No newline at end of file +} + +flag { + name: "use_cpu_time_capability" + namespace: "backstage_power" + description: "Use PROCESS_CAPABILITY_CPU_TIME to control unfreeze state." + bug: "370817323" +} diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java index ae93991d3945..0855815b67a9 100644 --- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java @@ -65,27 +65,31 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { @Override public boolean setGlobalRestriction(Object clientToken, int code, boolean restricted) { + boolean changed; if (restricted) { if (!mGlobalRestrictions.containsKey(clientToken)) { mGlobalRestrictions.put(clientToken, new SparseBooleanArray()); } SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken); Objects.requireNonNull(restrictedCodes); - boolean changed = !restrictedCodes.get(code); + changed = !restrictedCodes.get(code); restrictedCodes.put(code, true); - return changed; } else { SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken); if (restrictedCodes == null) { return false; } - boolean changed = restrictedCodes.get(code); + changed = restrictedCodes.get(code); restrictedCodes.delete(code); if (restrictedCodes.size() == 0) { mGlobalRestrictions.remove(clientToken); } - return changed; } + + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } + return changed; } @Override @@ -104,7 +108,11 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { @Override public boolean clearGlobalRestrictions(Object clientToken) { - return mGlobalRestrictions.remove(clientToken) != null; + boolean changed = mGlobalRestrictions.remove(clientToken) != null; + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } + return changed; } @RequiresPermission(anyOf = { @@ -122,6 +130,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { changed |= putUserRestrictionExclusions(clientToken, userIds[i], excludedPackageTags); } + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } return changed; } @@ -191,6 +202,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { changed |= mUserRestrictions.remove(clientToken) != null; changed |= mUserRestrictionExcludedPackageTags.remove(clientToken) != null; notifyAllUserRestrictions(allUserRestrictedCodes); + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } return changed; } @@ -244,6 +258,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { } } + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } return changed; } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 702ad9541af8..5e74d67905a6 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -998,6 +998,7 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void onUidModeChanged(int uid, int code, int mode, String persistentDeviceId) { + AppOpsManager.invalidateAppOpModeCache(); mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChangedForAllPkgsInUid, AppOpsService.this, code, uid, false, persistentDeviceId)); @@ -1006,6 +1007,7 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void onPackageModeChanged(String packageName, int userId, int code, int mode) { + AppOpsManager.invalidateAppOpModeCache(); mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChangedForPkg, AppOpsService.this, packageName, code, mode, userId)); @@ -1032,6 +1034,11 @@ public class AppOpsService extends IAppOpsService.Stub { // To migrate storageFile to recentAccessesFile, these reads must be called in this order. readRecentAccesses(); mAppOpsCheckingService.readState(); + // The system property used by the cache is created the first time it is written, that only + // happens inside invalidateCache(). Until the service calls invalidateCache() the property + // will not exist and the nonce will be UNSET. + AppOpsManager.invalidateAppOpModeCache(); + AppOpsManager.disableAppOpModeCache(); } public void publish() { @@ -2830,6 +2837,13 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int checkOperationRaw(int code, int uid, String packageName, @Nullable String attributionTag) { + if (Binder.getCallingPid() != Process.myPid() + && Flags.appopAccessTrackingLoggingEnabled()) { + FrameworkStatsLog.write( + APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code, + APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION, + false); + } return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag, Context.DEVICE_ID_DEFAULT, true /*raw*/); } @@ -2837,6 +2851,13 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int checkOperationRawForDevice(int code, int uid, @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId) { + if (Binder.getCallingPid() != Process.myPid() + && Flags.appopAccessTrackingLoggingEnabled()) { + FrameworkStatsLog.write( + APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code, + APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION, + false); + } return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag, virtualDeviceId, true /*raw*/); } @@ -2894,8 +2915,14 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_IGNORED; } } - return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, - virtualDeviceId, raw); + + if (Flags.appopModeCachingEnabled()) { + return getAppOpMode(code, uid, resolvedPackageName, attributionTag, virtualDeviceId, + raw, true); + } else { + return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, + virtualDeviceId, raw); + } } /** @@ -2961,6 +2988,54 @@ public class AppOpsService extends IAppOpsService.Stub { } } + /** + * This method unifies mode checking logic between checkOperationUnchecked and + * noteOperationUnchecked. It can replace those two methods once the flag is fully rolled out. + * + * @param isCheckOp This param is only used in user's op restriction. When checking if a package + * can bypass user's restriction we should account for attributionTag as well. + * But existing checkOp APIs don't accept attributionTag so we added a hack to + * skip attributionTag check for checkOp. After we add an overload of checkOp + * that accepts attributionTag we should remove this param. + */ + private @Mode int getAppOpMode(int code, int uid, @NonNull String packageName, + @Nullable String attributionTag, int virtualDeviceId, boolean raw, boolean isCheckOp) { + PackageVerificationResult pvr; + try { + pvr = verifyAndGetBypass(uid, packageName, attributionTag); + } catch (SecurityException e) { + logVerifyAndGetBypassFailure(uid, e, "getAppOpMode"); + return MODE_IGNORED; + } + + if (isOpRestrictedDueToSuspend(code, packageName, uid)) { + return MODE_IGNORED; + } + + synchronized (this) { + if (isOpRestrictedLocked(uid, code, packageName, attributionTag, virtualDeviceId, + pvr.bypass, isCheckOp)) { + return MODE_IGNORED; + } + if (isOpAllowedForUid(uid)) { + return MODE_ALLOWED; + } + + int switchCode = AppOpsManager.opToSwitch(code); + int rawUidMode = mAppOpsCheckingService.getUidMode(uid, + getPersistentId(virtualDeviceId), switchCode); + + if (rawUidMode != AppOpsManager.opToDefaultMode(switchCode)) { + return raw ? rawUidMode : evaluateForegroundMode(uid, switchCode, rawUidMode); + } + + int rawPackageMode = mAppOpsCheckingService.getPackageMode(packageName, switchCode, + UserHandle.getUserId(uid)); + return raw ? rawPackageMode : evaluateForegroundMode(uid, switchCode, rawPackageMode); + } + } + + @Override public int checkAudioOperation(int code, int usage, int uid, String packageName) { return mCheckOpsDelegateDispatcher.checkAudioOperation(code, usage, uid, packageName); @@ -3213,7 +3288,6 @@ public class AppOpsService extends IAppOpsService.Stub { PackageVerificationResult pvr; try { pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); - boolean wasNull = attributionTag == null; if (!pvr.isAttributionTagValid) { attributionTag = null; } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 421145390190..dabef84fec31 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -51,6 +51,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.Message; +import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemProperties; import android.util.ArrayMap; @@ -64,6 +65,7 @@ import android.view.SurfaceControl; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.brightness.BrightnessUtils; import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; @@ -323,7 +325,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { private int mWidth; private int mHeight; private int mDensityDpi; - private float mRequestedRefreshRate; + private final float mRequestedRefreshRate; private Surface mSurface; private DisplayDeviceInfo mInfo; private int mDisplayState; @@ -332,7 +334,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { private Display.Mode mMode; private int mDisplayIdToMirror; private boolean mIsWindowManagerMirroring; - private DisplayCutout mDisplayCutout; + private final DisplayCutout mDisplayCutout; + private final float mDefaultBrightness; + private float mCurrentBrightness; public VirtualDisplayDevice(IBinder displayToken, IBinder appToken, int ownerUid, String ownerPackageName, Surface surface, int flags, @@ -349,6 +353,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mDensityDpi = virtualDisplayConfig.getDensityDpi(); mRequestedRefreshRate = virtualDisplayConfig.getRequestedRefreshRate(); mDisplayCutout = virtualDisplayConfig.getDisplayCutout(); + mDefaultBrightness = virtualDisplayConfig.getDefaultBrightness(); + mCurrentBrightness = mDefaultBrightness; mMode = createMode(mWidth, mHeight, getRefreshRate()); mSurface = surface; mFlags = flags; @@ -457,6 +463,12 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mCallback.dispatchDisplayResumed(); } } + if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower() + && BrightnessUtils.isValidBrightnessValue(brightnessState) + && brightnessState != mCurrentBrightness) { + mCurrentBrightness = brightnessState; + mCallback.dispatchRequestedBrightnessChanged(mCurrentBrightness); + } return null; } @@ -623,6 +635,10 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mInfo.state = mDisplayState; } + mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN; + mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX; + mInfo.brightnessDefault = mDefaultBrightness; + mInfo.ownerUid = mOwnerUid; mInfo.ownerPackageName = mOwnerPackageName; @@ -642,6 +658,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { private static final int MSG_ON_DISPLAY_PAUSED = 0; private static final int MSG_ON_DISPLAY_RESUMED = 1; private static final int MSG_ON_DISPLAY_STOPPED = 2; + private static final int MSG_ON_REQUESTED_BRIGHTNESS_CHANGED = 3; private final IVirtualDisplayCallback mCallback; @@ -663,6 +680,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { case MSG_ON_DISPLAY_STOPPED: mCallback.onStopped(); break; + case MSG_ON_REQUESTED_BRIGHTNESS_CHANGED: + mCallback.onRequestedBrightnessChanged((Float) msg.obj); + break; } } catch (RemoteException e) { Slog.w(TAG, "Failed to notify listener of virtual display event.", e); @@ -677,6 +697,11 @@ public class VirtualDisplayAdapter extends DisplayAdapter { sendEmptyMessage(MSG_ON_DISPLAY_RESUMED); } + public void dispatchRequestedBrightnessChanged(float brightness) { + Message msg = obtainMessage(MSG_ON_REQUESTED_BRIGHTNESS_CHANGED, brightness); + sendMessage(msg); + } + public void dispatchDisplayStopped() { sendEmptyMessage(MSG_ON_DISPLAY_STOPPED); } diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java index 146ce1732070..46be1ca0eec2 100644 --- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java +++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java @@ -27,6 +27,7 @@ import android.util.ArrayMap; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; +import android.view.inputmethod.Flags; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; @@ -59,8 +60,14 @@ final class AdditionalSubtypeUtils { private static final String NODE_SUBTYPE = "subtype"; private static final String NODE_IMI = "imi"; private static final String ATTR_ID = "id"; + /** The resource ID of the subtype name. */ private static final String ATTR_LABEL = "label"; + /** The untranslatable name of the subtype. */ private static final String ATTR_NAME_OVERRIDE = "nameOverride"; + /** The layout label string resource identifier. */ + private static final String ATTR_LAYOUT_LABEL = "layoutLabel"; + /** The non-localized layout label. */ + private static final String ATTR_LAYOUT_LABEL_NON_LOCALIZED = "layoutLabelNonLocalized"; private static final String ATTR_NAME_PK_LANGUAGE_TAG = "pkLanguageTag"; private static final String ATTR_NAME_PK_LAYOUT_TYPE = "pkLayoutType"; private static final String ATTR_ICON = "icon"; @@ -173,6 +180,11 @@ final class AdditionalSubtypeUtils { out.attributeInt(null, ATTR_ICON, subtype.getIconResId()); out.attributeInt(null, ATTR_LABEL, subtype.getNameResId()); out.attribute(null, ATTR_NAME_OVERRIDE, subtype.getNameOverride().toString()); + if (Flags.imeSwitcherRevampApi()) { + out.attributeInt(null, ATTR_LAYOUT_LABEL, subtype.getLayoutLabelResource()); + out.attribute(null, ATTR_LAYOUT_LABEL_NON_LOCALIZED, + subtype.getLayoutLabelNonLocalized().toString()); + } ULocale pkLanguageTag = subtype.getPhysicalKeyboardHintLanguageTag(); if (pkLanguageTag != null) { out.attribute(null, ATTR_NAME_PK_LANGUAGE_TAG, @@ -264,6 +276,16 @@ final class AdditionalSubtypeUtils { final int label = parser.getAttributeInt(null, ATTR_LABEL); final String untranslatableName = parser.getAttributeValue(null, ATTR_NAME_OVERRIDE); + final int layoutLabelResource; + final String layoutLabelNonLocalized; + if (Flags.imeSwitcherRevampApi()) { + layoutLabelResource = parser.getAttributeInt(null, ATTR_LAYOUT_LABEL); + layoutLabelNonLocalized = parser.getAttributeValue(null, + ATTR_LAYOUT_LABEL_NON_LOCALIZED); + } else { + layoutLabelResource = 0; + layoutLabelNonLocalized = null; + } final String pkLanguageTag = parser.getAttributeValue(null, ATTR_NAME_PK_LANGUAGE_TAG); final String pkLayoutType = parser.getAttributeValue(null, @@ -283,6 +305,7 @@ final class AdditionalSubtypeUtils { final InputMethodSubtype.InputMethodSubtypeBuilder builder = new InputMethodSubtype.InputMethodSubtypeBuilder() .setSubtypeNameResId(label) + .setLayoutLabelResource(layoutLabelResource) .setPhysicalKeyboardHint( pkLanguageTag == null ? null : new ULocale(pkLanguageTag), pkLayoutType == null ? "" : pkLayoutType) @@ -301,6 +324,9 @@ final class AdditionalSubtypeUtils { if (untranslatableName != null) { builder.setSubtypeNameOverride(untranslatableName); } + if (layoutLabelNonLocalized != null) { + builder.setLayoutLabelNonLocalized(layoutLabelNonLocalized); + } tempSubtypesArray.add(builder.build()); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java index 6abd5aabfabf..9f94905686fe 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java @@ -238,8 +238,8 @@ final class InputMethodMenuControllerNew { prevImeId = imeId; } - menuItems.add(new SubtypeItem(item.mImeName, item.mSubtypeName, item.mImi, - item.mSubtypeIndex)); + menuItems.add(new SubtypeItem(item.mImeName, item.mSubtypeName, item.mLayoutName, + item.mImi, item.mSubtypeIndex)); } return menuItems; @@ -348,6 +348,13 @@ final class InputMethodMenuControllerNew { @Nullable final CharSequence mSubtypeName; + /** + * The name of the subtype's layout, or {@code null} if this item doesn't have a subtype, + * or doesn't specify a layout. + */ + @Nullable + private final CharSequence mLayoutName; + /** The info of the input method. */ @NonNull final InputMethodInfo mImi; @@ -360,10 +367,11 @@ final class InputMethodMenuControllerNew { final int mSubtypeIndex; SubtypeItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName, - @NonNull InputMethodInfo imi, + @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi, @IntRange(from = NOT_A_SUBTYPE_INDEX) int subtypeIndex) { mImeName = imeName; mSubtypeName = subtypeName; + mLayoutName = layoutName; mImi = imi; mSubtypeIndex = subtypeIndex; } @@ -521,6 +529,9 @@ final class InputMethodMenuControllerNew { /** The name of the item. */ @NonNull private final TextView mName; + /** The layout name. */ + @NonNull + private final TextView mLayout; /** Indicator for the selected status of the item. */ @NonNull private final ImageView mCheckmark; @@ -536,6 +547,7 @@ final class InputMethodMenuControllerNew { mContainer = itemView; mName = itemView.requireViewById(com.android.internal.R.id.text); + mLayout = itemView.requireViewById(com.android.internal.R.id.text2); mCheckmark = itemView.requireViewById(com.android.internal.R.id.image); mContainer.setOnClickListener((v) -> { @@ -563,6 +575,9 @@ final class InputMethodMenuControllerNew { // Trigger the ellipsize marquee behaviour by selecting the name. mName.setSelected(isSelected); mName.setText(name); + mLayout.setText(item.mLayoutName); + mLayout.setVisibility( + !TextUtils.isEmpty(item.mLayoutName) ? View.VISIBLE : View.GONE); mCheckmark.setVisibility(isSelected ? View.VISIBLE : View.GONE); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index 96b3e084d102..51b85e90c447 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -85,6 +85,12 @@ final class InputMethodSubtypeSwitchingController { public final CharSequence mImeName; @Nullable public final CharSequence mSubtypeName; + /** + * The subtype's layout name, or {@code null} if this item doesn't have a subtype, + * or doesn't specify a layout. + */ + @Nullable + public final CharSequence mLayoutName; @NonNull public final InputMethodInfo mImi; /** @@ -96,10 +102,11 @@ final class InputMethodSubtypeSwitchingController { public final boolean mIsSystemLanguage; ImeSubtypeListItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName, - @NonNull InputMethodInfo imi, int subtypeIndex, @Nullable String subtypeLocale, - @NonNull String systemLocale) { + @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi, int subtypeIndex, + @Nullable String subtypeLocale, @NonNull String systemLocale) { mImeName = imeName; mSubtypeName = subtypeName; + mLayoutName = layoutName; mImi = imi; mSubtypeIndex = subtypeIndex; if (TextUtils.isEmpty(subtypeLocale)) { @@ -252,8 +259,11 @@ final class InputMethodSubtypeSwitchingController { subtype.overridesImplicitlyEnabledSubtype() ? null : subtype .getDisplayName(userAwareContext, imi.getPackageName(), imi.getServiceInfo().applicationInfo); - imList.add(new ImeSubtypeListItem(imeLabel, - subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr)); + final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null + : subtype.getLayoutDisplayName(userAwareContext, + imi.getServiceInfo().applicationInfo); + imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName, + imi, j, subtype.getLocale(), mSystemLocaleStr)); // Removing this subtype from enabledSubtypeSet because we no // longer need to add an entry of this subtype to imList to avoid @@ -262,8 +272,8 @@ final class InputMethodSubtypeSwitchingController { } } } else { - imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null, - mSystemLocaleStr)); + imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */, + null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr)); } } Collections.sort(imList); @@ -311,13 +321,16 @@ final class InputMethodSubtypeSwitchingController { subtype.overridesImplicitlyEnabledSubtype() ? null : subtype .getDisplayName(userAwareContext, imi.getPackageName(), imi.getServiceInfo().applicationInfo); - imList.add(new ImeSubtypeListItem(imeLabel, - subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr)); + final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null + : subtype.getLayoutDisplayName(userAwareContext, + imi.getServiceInfo().applicationInfo); + imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName, + imi, j, subtype.getLocale(), mSystemLocaleStr)); } } } else { - imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null, - mSystemLocaleStr)); + imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */, + null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr)); } } return imList; diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index 89555a9f1de4..c8a87994ee16 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -161,6 +161,11 @@ public class MediaSession2Record extends MediaSessionRecordImpl { } @Override + public void onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive) { + // NA as MediaSession2 doesn't support UserEngagementStates for FGS. + } + + @Override public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService, KeyEvent ke, int sequenceId, ResultReceiver cb) { // TODO(jaewan): Implement. diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index d752429e64f7..668ee2adbd9f 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -230,51 +230,49 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde private final Runnable mUserEngagementTimeoutExpirationRunnable = () -> { synchronized (mLock) { - updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true); + updateUserEngagedStateIfNeededLocked( + /* isTimeoutExpired= */ true, + /* isGlobalPrioritySessionActive= */ false); } }; @GuardedBy("mLock") private @UserEngagementState int mUserEngagementState = USER_DISENGAGED; - @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARY_ENGAGED, USER_DISENGAGED}) + @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARILY_ENGAGED, USER_DISENGAGED}) @Retention(RetentionPolicy.SOURCE) private @interface UserEngagementState {} /** - * Indicates that the session is active and in one of the user engaged states. + * Indicates that the session is {@linkplain MediaSession#isActive() active} and in one of the + * {@linkplain PlaybackState#isActive() active states}. * * @see #updateUserEngagedStateIfNeededLocked(boolean) */ private static final int USER_PERMANENTLY_ENGAGED = 0; /** - * Indicates that the session is active and in {@link PlaybackState#STATE_PAUSED} state. + * Indicates that the session is {@linkplain MediaSession#isActive() active} and has recently + * switched to one of the {@linkplain PlaybackState#isActive() inactive states}. * * @see #updateUserEngagedStateIfNeededLocked(boolean) */ - private static final int USER_TEMPORARY_ENGAGED = 1; + private static final int USER_TEMPORARILY_ENGAGED = 1; /** - * Indicates that the session is either not active or in one of the user disengaged states + * Indicates that the session is either not {@linkplain MediaSession#isActive() active} or in + * one of the {@linkplain PlaybackState#isActive() inactive states}. * * @see #updateUserEngagedStateIfNeededLocked(boolean) */ private static final int USER_DISENGAGED = 2; /** - * Indicates the duration of the temporary engaged states, in milliseconds. + * Indicates the duration of the temporary engaged state, in milliseconds. * - * <p>Some {@link MediaSession} states like {@link PlaybackState#STATE_PAUSED} are temporarily - * engaged, meaning the corresponding session is only considered in an engaged state for the - * duration of this timeout, and only if coming from an engaged state. - * - * <p>For example, if a session is transitioning from a user-engaged state {@link - * PlaybackState#STATE_PLAYING} to a temporary user-engaged state {@link - * PlaybackState#STATE_PAUSED}, then the session will be considered in a user-engaged state for - * the duration of this timeout, starting at the transition instant. However, a temporary - * user-engaged state is not considered user-engaged when transitioning from a non-user engaged - * state {@link PlaybackState#STATE_STOPPED}. + * <p>When switching to an {@linkplain PlaybackState#isActive() inactive state}, the user is + * treated as temporarily engaged, meaning the corresponding session is only considered in an + * engaged state for the duration of this timeout, and only if coming from an engaged state. */ private static final int TEMP_USER_ENGAGED_TIMEOUT_MS = 600000; @@ -598,7 +596,8 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde mSessionCb.mCb.asBinder().unlinkToDeath(this, 0); mDestroyed = true; mPlaybackState = null; - updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true); + updateUserEngagedStateIfNeededLocked( + /* isTimeoutExpired= */ true, /* isGlobalPrioritySessionActive= */ false); mHandler.post(MessageHandler.MSG_DESTROYED); } } @@ -615,6 +614,24 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde mHandler.post(mUserEngagementTimeoutExpirationRunnable); } + @Override + public void onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive) { + mHandler.post( + () -> { + synchronized (mLock) { + if (isGlobalPrioritySessionActive) { + mHandler.removeCallbacks(mUserEngagementTimeoutExpirationRunnable); + } else { + if (mUserEngagementState == USER_TEMPORARILY_ENGAGED) { + mHandler.postDelayed( + mUserEngagementTimeoutExpirationRunnable, + TEMP_USER_ENGAGED_TIMEOUT_MS); + } + } + } + }); + } + /** * Sends media button. * @@ -1063,21 +1080,20 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } @GuardedBy("mLock") - private void updateUserEngagedStateIfNeededLocked(boolean isTimeoutExpired) { + private void updateUserEngagedStateIfNeededLocked( + boolean isTimeoutExpired, boolean isGlobalPrioritySessionActive) { if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) { return; } int oldUserEngagedState = mUserEngagementState; int newUserEngagedState; - if (!isActive() || mPlaybackState == null || mDestroyed) { + if (!isActive() || mPlaybackState == null) { newUserEngagedState = USER_DISENGAGED; - } else if (isActive() && mPlaybackState.isActive()) { + } else if (mPlaybackState.isActive()) { newUserEngagedState = USER_PERMANENTLY_ENGAGED; - } else if (mPlaybackState.getState() == PlaybackState.STATE_PAUSED) { - newUserEngagedState = - oldUserEngagedState == USER_PERMANENTLY_ENGAGED || !isTimeoutExpired - ? USER_TEMPORARY_ENGAGED - : USER_DISENGAGED; + } else if (oldUserEngagedState == USER_PERMANENTLY_ENGAGED + || (oldUserEngagedState == USER_TEMPORARILY_ENGAGED && !isTimeoutExpired)) { + newUserEngagedState = USER_TEMPORARILY_ENGAGED; } else { newUserEngagedState = USER_DISENGAGED; } @@ -1086,7 +1102,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } mUserEngagementState = newUserEngagedState; - if (newUserEngagedState == USER_TEMPORARY_ENGAGED) { + if (newUserEngagedState == USER_TEMPORARILY_ENGAGED && !isGlobalPrioritySessionActive) { mHandler.postDelayed( mUserEngagementTimeoutExpirationRunnable, TEMP_USER_ENGAGED_TIMEOUT_MS); } else { @@ -1141,9 +1157,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK, callingUid, callingPid); } + boolean isGlobalPrioritySessionActive = mService.isGlobalPrioritySessionActive(); synchronized (mLock) { mIsActive = active; - updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false); + updateUserEngagedStateIfNeededLocked( + /* isTimeoutExpired= */ false, isGlobalPrioritySessionActive); } long token = Binder.clearCallingIdentity(); try { @@ -1300,9 +1318,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState) || (!TRANSITION_PRIORITY_STATES.contains(oldState) && TRANSITION_PRIORITY_STATES.contains(newState)); + boolean isGlobalPrioritySessionActive = mService.isGlobalPrioritySessionActive(); synchronized (mLock) { mPlaybackState = state; - updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false); + updateUserEngagedStateIfNeededLocked( + /* isTimeoutExpired= */ false, isGlobalPrioritySessionActive); } final long token = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java index 15f90d4fdd0e..6c3b1234935a 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java @@ -206,6 +206,10 @@ public abstract class MediaSessionRecordImpl { */ public abstract void expireTempEngaged(); + /** Notifies record that the global priority session active state changed. */ + public abstract void onGlobalPrioritySessionActiveChanged( + boolean isGlobalPrioritySessionActive); + @Override public final boolean equals(Object o) { if (this == o) return true; diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 1ebc856af2d8..2b29fbd9c5b5 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -362,6 +362,7 @@ public class MediaSessionService extends SystemService implements Monitor { + record.isActive()); } user.pushAddressedPlayerChangedLocked(); + mHandler.post(this::notifyGlobalPrioritySessionActiveChanged); } else { if (!user.mPriorityStack.contains(record)) { Log.w(TAG, "Unknown session updated. Ignoring."); @@ -394,11 +395,16 @@ public class MediaSessionService extends SystemService implements Monitor { // Currently only media1 can become global priority session. void setGlobalPrioritySession(MediaSessionRecord record) { + boolean globalPrioritySessionActiveChanged = false; synchronized (mLock) { FullUserRecord user = getFullUserRecordLocked(record.getUserId()); if (mGlobalPrioritySession != record) { Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession + " to " + record); + globalPrioritySessionActiveChanged = + (mGlobalPrioritySession == null && record.isActive()) + || (mGlobalPrioritySession != null + && mGlobalPrioritySession.isActive() != record.isActive()); mGlobalPrioritySession = record; if (user != null && user.mPriorityStack.contains(record)) { // Handle the global priority session separately. @@ -409,6 +415,30 @@ public class MediaSessionService extends SystemService implements Monitor { } } } + if (globalPrioritySessionActiveChanged) { + mHandler.post(this::notifyGlobalPrioritySessionActiveChanged); + } + } + + /** Returns whether the global priority session is active. */ + boolean isGlobalPrioritySessionActive() { + synchronized (mLock) { + return isGlobalPriorityActiveLocked(); + } + } + + private void notifyGlobalPrioritySessionActiveChanged() { + if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) { + return; + } + synchronized (mLock) { + boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked(); + for (Set<MediaSessionRecordImpl> records : mUserEngagedSessionsForFgs.values()) { + for (MediaSessionRecordImpl record : records) { + record.onGlobalPrioritySessionActiveChanged(isGlobalPriorityActive); + } + } + } } private List<MediaSessionRecord> getActiveSessionsLocked(int userId) { @@ -646,8 +676,11 @@ public class MediaSessionService extends SystemService implements Monitor { if (mGlobalPrioritySession == session) { mGlobalPrioritySession = null; - if (session.isActive() && user != null) { - user.pushAddressedPlayerChangedLocked(); + if (session.isActive()) { + if (user != null) { + user.pushAddressedPlayerChangedLocked(); + } + mHandler.post(this::notifyGlobalPrioritySessionActiveChanged); } } else { if (user != null) { diff --git a/services/core/java/com/android/server/notification/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/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java index 015b7fd74211..38f39393a025 100644 --- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java +++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java @@ -19,6 +19,7 @@ package com.android.server.om; import android.annotation.NonNull; import android.content.om.OverlayInfo; import android.content.om.OverlayableInfo; +import android.content.res.Flags; import android.net.Uri; import android.os.Process; import android.text.TextUtils; @@ -162,11 +163,15 @@ public class OverlayActorEnforcer { return ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE; } - if (targetOverlayable == null) { + // Framework doesn't have <overlayable> declaration by design, and we still want to be able + // to enable its overlays from the packages with the permission. + if (targetOverlayable == null + && !(Flags.rroControlForAndroidNoOverlayable() && targetPackageName.equals( + "android"))) { return ActorState.MISSING_OVERLAYABLE; } - String actor = targetOverlayable.actor; + final String actor = targetOverlayable == null ? null : targetOverlayable.actor; if (TextUtils.isEmpty(actor)) { // If there's no actor defined, fallback to the legacy permission check try { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 7ecfe7f64ffe..498659427a21 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -341,6 +341,10 @@ public class UserManagerService extends IUserManager.Stub { private static final String TRON_USER_CREATED = "users_user_created"; private static final String TRON_DEMO_CREATED = "users_demo_created"; + // The boot user strategy for HSUM. + private static final int BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER = 0; + private static final int BOOT_TO_HSU_FOR_PROVISIONED_DEVICE = 1; + private final Context mContext; private final PackageManagerService mPm; @@ -1391,37 +1395,77 @@ public class UserManagerService extends IUserManager.Stub { } if (isHeadlessSystemUserMode()) { - if (mContext.getResources() - .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser)) { - return UserHandle.USER_SYSTEM; - } - // Return the previous foreground user, if there is one. - final int previousUser = getPreviousFullUserToEnterForeground(); - if (previousUser != UserHandle.USER_NULL) { - Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser); - return previousUser; - } - // No previous user. Return the first switchable user if there is one. - synchronized (mUsersLock) { - final int userSize = mUsers.size(); - for (int i = 0; i < userSize; i++) { - final UserData userData = mUsers.valueAt(i); - if (userData.info.supportsSwitchToByUser()) { - int firstSwitchable = userData.info.id; - Slogf.i(LOG_TAG, - "Boot user is first switchable user %d", firstSwitchable); - return firstSwitchable; - } - } + final int bootStrategy = mContext.getResources() + .getInteger(com.android.internal.R.integer.config_hsumBootStrategy); + switch (bootStrategy) { + case BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER: + return getPreviousOrFirstSwitchableUser(); + case BOOT_TO_HSU_FOR_PROVISIONED_DEVICE: + return getBootUserBasedOnProvisioning(); + default: + Slogf.w(LOG_TAG, "Unknown HSUM boot strategy: %d", bootStrategy); + return getPreviousOrFirstSwitchableUser(); } - // No switchable users found. Uh oh! - throw new UserManager.CheckedUserOperationException( - "No switchable users found", USER_OPERATION_ERROR_UNKNOWN); } // Not HSUM, return system user. return UserHandle.USER_SYSTEM; } + private @UserIdInt int getBootUserBasedOnProvisioning() + throws UserManager.CheckedUserOperationException { + final boolean provisioned = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0) != 0; + if (provisioned) { + return UserHandle.USER_SYSTEM; + } else { + final int firstSwitchableFullUser = getFirstSwitchableUser(true); + if (firstSwitchableFullUser != UserHandle.USER_NULL) { + Slogf.i(LOG_TAG, + "Boot user is first switchable full user %d", + firstSwitchableFullUser); + return firstSwitchableFullUser; + } + // No switchable full user found. Uh oh! + throw new UserManager.CheckedUserOperationException( + "No switchable full user found", USER_OPERATION_ERROR_UNKNOWN); + } + } + + private @UserIdInt int getPreviousOrFirstSwitchableUser() + throws UserManager.CheckedUserOperationException { + // Return the previous foreground user, if there is one. + final int previousUser = getPreviousFullUserToEnterForeground(); + if (previousUser != UserHandle.USER_NULL) { + Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser); + return previousUser; + } + // No previous user. Return the first switchable user if there is one. + final int firstSwitchableUser = getFirstSwitchableUser(false); + if (firstSwitchableUser != UserHandle.USER_NULL) { + Slogf.i(LOG_TAG, + "Boot user is first switchable user %d", firstSwitchableUser); + return firstSwitchableUser; + } + // No switchable users found. Uh oh! + throw new UserManager.CheckedUserOperationException( + "No switchable users found", USER_OPERATION_ERROR_UNKNOWN); + } + + private @UserIdInt int getFirstSwitchableUser(boolean fullUserOnly) { + synchronized (mUsersLock) { + final int userSize = mUsers.size(); + for (int i = 0; i < userSize; i++) { + final UserData userData = mUsers.valueAt(i); + if (userData.info.supportsSwitchToByUser() && + (!fullUserOnly || userData.info.isFull())) { + int firstSwitchable = userData.info.id; + return firstSwitchable; + } + } + } + return UserHandle.USER_NULL; + } + @Override public int getPreviousFullUserToEnterForeground() { diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 07fd1cb544f6..acf62dcdd1ce 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -235,6 +235,7 @@ final class DefaultPermissionGrantPolicy { NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN); NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.UWB_RANGING); NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.NEARBY_WIFI_DEVICES); + NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.RANGING); } private static final Set<String> NOTIFICATION_PERMISSIONS = new ArraySet<>(); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 24933cab2a32..5fc3e332b95c 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1015,8 +1015,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { permission, attributionSource, message, forDataDelivery, startDataDelivery, fromDatasource, attributedOp); // Finish any started op if some step in the attribution chain failed. - if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED - && result != PermissionChecker.PERMISSION_SOFT_DENIED) { + if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) { if (attributedOp == AppOpsManager.OP_NONE) { finishDataDelivery(AppOpsManager.permissionToOpCode(permission), attributionSource.asState(), fromDatasource); @@ -1245,7 +1244,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE; AttributionSource current = attributionSource; AttributionSource next = null; - AttributionSource prev = null; // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and // every attributionSource in the chain is registered with the system. final boolean isChainStartTrusted = !hasChain || checkPermission(context, @@ -1312,21 +1310,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { selfAccess, singleReceiverFromDatasource, attributedOp, proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); - if (opMode != AppOpsManager.MODE_ALLOWED) { - // Current failed the perm check, so if we are part-way through an attr chain, - // we need to clean up the already started proxy op higher up the chain. Note, - // proxy ops are verified two by two, which means we have to clear the 2nd next - // from the previous iteration (since it is actually curr.next which failed - // to pass the perm check). - if (prev != null) { - final var cutAttrSourceState = prev.asState(); - if (cutAttrSourceState.next.length > 0) { - cutAttrSourceState.next[0].next = new AttributionSourceState[0]; - } - finishDataDelivery(context, attributedOp, - cutAttrSourceState, fromDatasource); - } - if (opMode == AppOpsManager.MODE_ERRORED) { + switch (opMode) { + case AppOpsManager.MODE_ERRORED: { if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) { Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as op" + " mode is MODE_ERRORED. Permission check was requested for: " @@ -1334,7 +1319,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { + current); } return PermissionChecker.PERMISSION_HARD_DENIED; - } else { + } + case AppOpsManager.MODE_IGNORED: { return PermissionChecker.PERMISSION_SOFT_DENIED; } } @@ -1349,8 +1335,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { return PermissionChecker.PERMISSION_GRANTED; } - // an attribution we have already possibly started an op for - prev = current; current = next; } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index cf178046d2e6..4857b02eaf7c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -1725,9 +1725,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { return; } Transition transit = task.mTransitionController.requestCloseTransitionIfNeeded(task); - if (transit == null) { - transit = task.mTransitionController.getCollectingTransition(); - } if (transit != null) { transit.collectClose(task); if (!task.mTransitionController.useFullReadyTracking()) { @@ -1739,7 +1736,15 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // before anything that may need it to wait (setReady(false)). transit.setReady(task, true); } + } else { + // If we failed to create a transition, there might be already a currently collecting + // transition. Let's use it if possible. + transit = task.mTransitionController.getCollectingTransition(); + if (transit != null) { + transit.collectClose(task); + } } + // Consume the stopping activities immediately so activity manager won't skip killing // the process because it is still foreground state, i.e. RESUMED -> PAUSING set from // removeActivities -> finishIfPossible. diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index db76eb9ac5d9..ebb50db54693 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -164,41 +164,46 @@ final class AppCompatUtils { appCompatTaskInfo.setIsFromLetterboxDoubleTap(reachabilityOverrides.isFromDoubleTap()); - final Rect bounds = top.getBounds(); - final Rect appBounds = getAppBounds(top); - appCompatTaskInfo.topActivityLetterboxWidth = bounds.width(); - appCompatTaskInfo.topActivityLetterboxHeight = bounds.height(); - appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width(); - appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height(); + final boolean isTopActivityLetterboxed = top.areBoundsLetterboxed(); + appCompatTaskInfo.setTopActivityLetterboxed(isTopActivityLetterboxed); + if (isTopActivityLetterboxed) { + final Rect bounds = top.getBounds(); + final Rect appBounds = getAppBounds(top); + appCompatTaskInfo.topActivityLetterboxWidth = bounds.width(); + appCompatTaskInfo.topActivityLetterboxHeight = bounds.height(); + appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width(); + appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height(); - // We need to consider if letterboxed or pillarboxed. - // TODO(b/336807329) Encapsulate reachability logic - appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides - .isLetterboxDoubleTapEducationEnabled()); - if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) { - if (appCompatTaskInfo.isTopActivityPillarboxed()) { - if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) { - // Pillarboxed. - appCompatTaskInfo.topActivityLetterboxHorizontalPosition = - reachabilityOverrides.getLetterboxPositionForHorizontalReachability(); - } else { - appCompatTaskInfo.setLetterboxDoubleTapEnabled(false); - } - } else { - if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) { - // Letterboxed. - appCompatTaskInfo.topActivityLetterboxVerticalPosition = - reachabilityOverrides.getLetterboxPositionForVerticalReachability(); + // We need to consider if letterboxed or pillarboxed. + // TODO(b/336807329) Encapsulate reachability logic + appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides + .isLetterboxDoubleTapEducationEnabled()); + if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) { + if (appCompatTaskInfo.isTopActivityPillarboxShaped()) { + if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) { + // Pillarboxed. + appCompatTaskInfo.topActivityLetterboxHorizontalPosition = + reachabilityOverrides + .getLetterboxPositionForHorizontalReachability(); + } else { + appCompatTaskInfo.setLetterboxDoubleTapEnabled(false); + } } else { - appCompatTaskInfo.setLetterboxDoubleTapEnabled(false); + if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) { + // Letterboxed. + appCompatTaskInfo.topActivityLetterboxVerticalPosition = + reachabilityOverrides.getLetterboxPositionForVerticalReachability(); + } else { + appCompatTaskInfo.setLetterboxDoubleTapEnabled(false); + } } } } + final boolean eligibleForAspectRatioButton = !info.isTopActivityTransparent && !appCompatTaskInfo.isTopActivityInSizeCompat() && aspectRatioOverrides.shouldEnableUserAspectRatioSettings(); appCompatTaskInfo.setEligibleForUserAspectRatioButton(eligibleForAspectRatioButton); - appCompatTaskInfo.setTopActivityLetterboxed(top.areBoundsLetterboxed()); appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = AppCompatCameraPolicy.getCameraCompatFreeformMode(top); appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index ee07d2e58389..76e8a70768c1 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1736,7 +1736,7 @@ public class DisplayPolicy { // Show IME over the keyguard if the target allows it. final boolean showImeOverKeyguard = - imeTarget != null && imeTarget.isOnScreen() && win.mIsImWindow && ( + imeTarget != null && win.mIsImWindow && imeTarget.isDisplayed() && ( imeTarget.canShowWhenLocked() || !imeTarget.canBeHiddenByKeyguard()); if (showImeOverKeyguard) { return false; diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 72d45e42125d..a6034664af5a 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -3586,6 +3586,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (wc.mWmService.mAtmService.mBackNavigationController.isMonitorTransitionTarget(wc)) { flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; } + final TaskDisplayArea tda = wc.asTaskDisplayArea(); + if (tda != null) { + flags |= TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA; + } final Task task = wc.asTask(); if (task != null) { final ActivityRecord topActivity = task.getTopNonFinishingActivity(); diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 5bde8b5a507c..44e237aa27de 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -614,6 +614,12 @@ class WindowToken extends WindowContainer<WindowState> { final int rotation = getRelativeDisplayRotation(); if (rotation == Surface.ROTATION_0) return mFixedRotationTransformLeash; if (mFixedRotationTransformLeash != null) return mFixedRotationTransformLeash; + if (ActivityTaskManagerService.isPip2ExperimentEnabled() && asActivityRecord() != null + && mTransitionController.getWindowingModeAtStart( + asActivityRecord()) == WINDOWING_MODE_PINNED) { + // PiP handles fixed rotation animation in Shell, so do not create the rotation leash. + return null; + } final SurfaceControl leash = makeSurface().setContainerLayer() .setParent(getParentSurfaceControl()) diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index aca6f7235714..4ce18d232936 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -255,7 +255,6 @@ import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPR import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; -import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; @@ -462,7 +461,6 @@ import android.permission.PermissionControllerManager; import android.provider.CalendarContract; import android.provider.ContactsContract.QuickContact; import android.provider.ContactsInternal; -import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Telephony; @@ -908,10 +906,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + "management app's authentication policy"; private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s"; - private static final String PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG = - "enable_permission_based_access"; - private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false; - private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3; /** @@ -3557,6 +3551,46 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return true; } + + + @GuardedBy("getLockObject()") + private boolean maybeMigrateMemoryTaggingLocked(String backupId) { + if (!Flags.setMtePolicyCoexistence()) { + Slog.i(LOG_TAG, "Memory Tagging not migrated because coexistence " + + "support is disabled."); + return false; + } + if (mOwners.isMemoryTaggingMigrated()) { + // TODO: Remove log after Flags.setMtePolicyCoexistence full rollout. + Slog.v(LOG_TAG, "Memory Tagging was previously migrated to policy engine."); + return false; + } + + Slog.i(LOG_TAG, "Migrating Memory Tagging to policy engine"); + + // Create backup if none exists + mDevicePolicyEngine.createBackup(backupId); + try { + iterateThroughDpcAdminsLocked((admin, enforcingAdmin) -> { + if (admin.mtePolicy != 0) { + Slog.i(LOG_TAG, "Setting Memory Tagging policy"); + mDevicePolicyEngine.setGlobalPolicy( + PolicyDefinition.MEMORY_TAGGING, + enforcingAdmin, + new IntegerPolicyValue(admin.mtePolicy), + true /* No need to re-set system properties */); + } + }); + } catch (Exception e) { + Slog.wtf(LOG_TAG, + "Failed to migrate Memory Tagging to policy engine", e); + } + + Slog.i(LOG_TAG, "Marking Memory Tagging migration complete"); + mOwners.markMemoryTaggingMigrated(); + return true; + } + /** Register callbacks for statsd pulled atoms. */ private void registerStatsCallbacks() { final StatsManager statsManager = mContext.getSystemService(StatsManager.class); @@ -4646,22 +4680,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @GuardedBy("getLockObject()") private List<ActiveAdmin> getActiveAdminsForLockscreenPoliciesLocked(int userHandle) { if (isSeparateProfileChallengeEnabled(userHandle)) { - - if (isPermissionCheckFlagEnabled()) { - return getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(userHandle); - } // If this user has a separate challenge, only return its restrictions. return getUserDataUnchecked(userHandle).mAdminList; } // If isSeparateProfileChallengeEnabled is false and userHandle points to a managed profile // we need to query the parent user who owns the credential. - if (isPermissionCheckFlagEnabled()) { - return getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(getProfileParentId(userHandle), - (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); - } else { - return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle), - (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); - } + return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle), + (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); } @@ -4684,33 +4709,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { (user) -> mLockPatternUtils.isProfileWithUnifiedChallenge(user.id)); } - /** - * Get the list of active admins for an affected user: - * <ul> - * <li>The active admins associated with the userHandle itself</li> - * <li>The parent active admins for each managed profile associated with the userHandle</li> - * <li>The permission based admin associated with the userHandle itself</li> - * </ul> - * - * @param userHandle the affected user for whom to get the active admins - * @return the list of active admins for the affected user - */ - @GuardedBy("getLockObject()") - private List<ActiveAdmin> getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked( - int userHandle) { - List<ActiveAdmin> list; - - if (isManagedProfile(userHandle)) { - list = getUserDataUnchecked(userHandle).mAdminList; - } - list = getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(userHandle, - /* shouldIncludeProfileAdmins */ (user) -> false); - - if (getUserData(userHandle).mPermissionBasedAdmin != null) { - list.add(getUserData(userHandle).mPermissionBasedAdmin); - } - return list; - } /** * Returns the list of admins on the given user, as well as parent admins for each managed @@ -4763,44 +4761,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mDevicePolicyEngine.getResolvedPolicyAcrossUsers(policyDefinition, users); } - /** - * Returns the list of admins on the given user, as well as parent admins for each managed - * profile associated with the given user. Optionally also include the admin of each managed - * profile. - * <p> Should not be called on a profile user. - */ - @GuardedBy("getLockObject()") - private List<ActiveAdmin> getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(int userHandle, - Predicate<UserInfo> shouldIncludeProfileAdmins) { - ArrayList<ActiveAdmin> admins = new ArrayList<>(); - mInjector.binderWithCleanCallingIdentity(() -> { - for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) { - DevicePolicyData policy = getUserDataUnchecked(userInfo.id); - if (userInfo.id == userHandle) { - admins.addAll(policy.mAdminList); - if (policy.mPermissionBasedAdmin != null) { - admins.add(policy.mPermissionBasedAdmin); - } - } else if (userInfo.isManagedProfile()) { - for (int i = 0; i < policy.mAdminList.size(); i++) { - ActiveAdmin admin = policy.mAdminList.get(i); - if (admin.hasParentActiveAdmin()) { - admins.add(admin.getParentActiveAdmin()); - } - if (shouldIncludeProfileAdmins.test(userInfo)) { - admins.add(admin); - } - } - if (policy.mPermissionBasedAdmin != null - && shouldIncludeProfileAdmins.test(userInfo)) { - admins.add(policy.mPermissionBasedAdmin); - } - } - } - }); - return admins; - } - private boolean isSeparateProfileChallengeEnabled(int userHandle) { return mInjector.binderWithCleanCallingIdentity(() -> mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle)); @@ -4893,25 +4853,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(who, "ComponentName is null"); - } + Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkArgumentNonnegative(timeout, "Timeout must be >= 0 ms"); int userHandle = mInjector.userHandleGetCallingUserId(); int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; synchronized (getLockObject()) { ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - CallerIdentity caller = getCallerIdentity(who, callerPackageName); - ap = enforcePermissionAndGetEnforcingAdmin( - who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - caller.getPackageName(), affectedUserId) - .getActiveAdmin(); - } else { - ap = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent); - } + ap = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent); // Calling this API automatically bumps the expiration date final long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L; ap.passwordExpirationDate = expiration; @@ -4972,28 +4922,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean addCrossProfileWidgetProvider(ComponentName admin, String callerPackageName, String packageName) { - CallerIdentity caller; + CallerIdentity caller = getCallerIdentity(admin); - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - } else { - caller = getCallerIdentity(admin); - } - ActiveAdmin activeAdmin; + Objects.requireNonNull(admin, "ComponentName is null"); + Preconditions.checkCallAuthorization(isProfileOwner(caller)); - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - admin, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - caller.getUserId()); - activeAdmin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(admin, "ComponentName is null"); - Preconditions.checkCallAuthorization(isProfileOwner(caller)); - synchronized (getLockObject()) { - activeAdmin = getProfileOwnerLocked(caller.getUserId()); - } + ActiveAdmin activeAdmin; + synchronized (getLockObject()) { + activeAdmin = getProfileOwnerLocked(caller.getUserId()); } List<String> changedProviders = null; @@ -5026,28 +4962,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean removeCrossProfileWidgetProvider(ComponentName admin, String callerPackageName, String packageName) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - } else { - caller = getCallerIdentity(admin); - } + CallerIdentity caller = getCallerIdentity(admin); - ActiveAdmin activeAdmin; + Objects.requireNonNull(admin, "ComponentName is null"); + Preconditions.checkCallAuthorization(isProfileOwner(caller)); - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - admin, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - caller.getUserId()); - activeAdmin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(admin, "ComponentName is null"); - Preconditions.checkCallAuthorization(isProfileOwner(caller)); - synchronized (getLockObject()) { - activeAdmin = getProfileOwnerLocked(caller.getUserId()); - } + ActiveAdmin activeAdmin; + synchronized (getLockObject()) { + activeAdmin = getProfileOwnerLocked(caller.getUserId()); } List<String> changedProviders = null; @@ -5080,27 +5002,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public List<String> getCrossProfileWidgetProviders(ComponentName admin, String callerPackageName) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - } else { - caller = getCallerIdentity(admin); - } - ActiveAdmin activeAdmin; + CallerIdentity caller = getCallerIdentity(admin); - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin( - admin, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - caller.getUserId()); - activeAdmin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(admin, "ComponentName is null"); - Preconditions.checkCallAuthorization(isProfileOwner(caller)); - synchronized (getLockObject()) { - activeAdmin = getProfileOwnerLocked(caller.getUserId()); - } + Objects.requireNonNull(admin, "ComponentName is null"); + Preconditions.checkCallAuthorization(isProfileOwner(caller)); + + ActiveAdmin activeAdmin; + synchronized (getLockObject()) { + activeAdmin = getProfileOwnerLocked(caller.getUserId()); } synchronized (getLockObject()) { @@ -5449,24 +5358,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforceUserUnlocked(userHandle, parent); synchronized (getLockObject()) { - if (isPermissionCheckFlagEnabled()) { - int affectedUser = parent ? getProfileParentId(userHandle) : userHandle; - enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - callerPackageName, affectedUser); - } else { - // This API can only be called by an active device admin, - // so try to retrieve it to check that the caller is one. - getActiveAdminForCallerLocked( - null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - } + // This API can only be called by an active device admin, + // so try to retrieve it to check that the caller is one. + getActiveAdminForCallerLocked( + null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); int credentialOwner = getCredentialOwner(userHandle, parent); DevicePolicyData policy = getUserDataUnchecked(credentialOwner); PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner); final int userToCheck = getProfileParentUserIfRequested(userHandle, parent); - boolean activePasswordSufficientForUserLocked = isActivePasswordSufficientForUserLocked( + return isActivePasswordSufficientForUserLocked( policy.mPasswordValidAtLastCheckpoint, metrics, userToCheck); - return activePasswordSufficientForUserLocked; } } @@ -5622,21 +5524,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller), "Only profile owner, device owner and system may call this method on parent."); } else { - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY) - || hasCallingOrSelfPermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS) - || isDefaultDeviceOwner(caller) || isProfileOwner(caller), - "Must have " + REQUEST_PASSWORD_COMPLEXITY + " or " + - MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS - + " permissions, or be a profile owner or device owner."); - } else { - Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY) - || isDefaultDeviceOwner(caller) || isProfileOwner(caller), - "Must have " + REQUEST_PASSWORD_COMPLEXITY - + " permission, or be a profile owner or device owner."); - } + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY) + || isDefaultDeviceOwner(caller) || isProfileOwner(caller), + "Must have " + REQUEST_PASSWORD_COMPLEXITY + + " permission, or be a profile owner or device owner."); } synchronized (getLockObject()) { @@ -5728,26 +5620,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void setRequiredPasswordComplexityPreCoexistence( String callerPackageName, int passwordComplexity, boolean calledOnParent) { CallerIdentity caller = getCallerIdentity(callerPackageName); - if (!isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwner(caller)); - Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller)); - } + + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller)); synchronized (getLockObject()) { ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - // TODO: Make sure this returns the parent of the fake admin - // TODO: Deal with null componentname - int affectedUser = calledOnParent - ? getProfileParentId(caller.getUserId()) : caller.getUserId(); - admin = enforcePermissionAndGetEnforcingAdmin( - null, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - caller.getPackageName(), affectedUser).getActiveAdmin(); - } else { - admin = getParentOfAdminIfRequired( - getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent); - } + admin = getParentOfAdminIfRequired( + getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent); if (admin.mPasswordComplexity != passwordComplexity) { // We require the caller to explicitly clear any password quality requirements set @@ -5907,14 +5788,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!isSystemUid(caller)) { // This API can be called by an active device admin or by keyguard code. if (!hasCallingPermission(permission.ACCESS_KEYGUARD_SECURE_STORAGE)) { - if (isPermissionCheckFlagEnabled()) { - int affectedUser = parent ? getProfileParentId(userHandle) : userHandle; - enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - callerPackageName, affectedUser); - } else { - getActiveAdminForCallerLocked( - null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); - } + getActiveAdminForCallerLocked( + null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); } } @@ -5931,31 +5806,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(who, "ComponentName is null"); - } - + Objects.requireNonNull(who, "ComponentName is null"); int userId = mInjector.userHandleGetCallingUserId(); int affectedUserId = parent ? getProfileParentId(userId) : userId; synchronized (getLockObject()) { - ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - CallerIdentity caller = getCallerIdentity(who, callerPackageName); - ap = enforcePermissionAndGetEnforcingAdmin( - who, - /*permission=*/ MANAGE_DEVICE_POLICY_WIPE_DATA, - /* adminPolicy=*/ DeviceAdminInfo.USES_POLICY_WIPE_DATA, - caller.getPackageName(), affectedUserId).getActiveAdmin(); - } else { - // This API can only be called by an active device admin, - // so try to retrieve it to check that the caller is one. - getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent); - ap = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); - } + // This API can only be called by an active device admin, + // so try to retrieve it to check that the caller is one. + getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent); + ActiveAdmin ap = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); if (ap.maximumFailedPasswordsForWipe != num) { ap.maximumFailedPasswordsForWipe = num; @@ -6210,25 +6072,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(who, "ComponentName is null"); - } + + Objects.requireNonNull(who, "ComponentName is null"); + int userHandle = mInjector.userHandleGetCallingUserId(); int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; synchronized (getLockObject()) { - ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - CallerIdentity caller = getCallerIdentity(who, callerPackageName); - ap = enforcePermissionAndGetEnforcingAdmin( - who, - /*permission=*/ MANAGE_DEVICE_POLICY_LOCK, - /*AdminPolicy=*/DeviceAdminInfo.USES_POLICY_FORCE_LOCK, - caller.getPackageName(), - affectedUserId).getActiveAdmin(); - } else { - ap = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent); - } + ActiveAdmin ap = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent); if (ap.maximumTimeToUnlock != timeMs) { ap.maximumTimeToUnlock = timeMs; @@ -6334,16 +6185,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } + Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number."); - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwner(caller)); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + // timeoutMs with value 0 means that the admin doesn't participate // timeoutMs is clamped to the interval in case the internal constants change in the future final long minimumStrongAuthTimeout = getMinimumStrongAuthTimeoutMs(); @@ -6357,17 +6205,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int userHandle = caller.getUserId(); boolean changed = false; synchronized (getLockObject()) { - ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - int affectedUser = parent - ? getProfileParentId(caller.getUserId()) : caller.getUserId(); - ap = enforcePermissionAndGetEnforcingAdmin( - who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - caller.getPackageName(), affectedUser).getActiveAdmin(); - } else { - ap = getParentOfAdminIfRequired( - getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent); - } + ActiveAdmin ap = getParentOfAdminIfRequired( + getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent); if (ap.strongAuthUnlockTimeout != timeoutMs) { ap.strongAuthUnlockTimeout = timeoutMs; saveSettingsLocked(userHandle); @@ -6664,16 +6503,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(who, callerPackage); final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL); final boolean isCredentialManagementApp = isCredentialManagementApp(caller); - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()) - || isCredentialManagementApp); - } else { - Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) - || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); - } + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) + || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization(!isUserSelectable, "The credential " + "management app is not allowed to install a user selectable key pair"); @@ -6733,16 +6565,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(who, callerPackage); final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL); final boolean isCredentialManagementApp = isCredentialManagementApp(caller); - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()) - || isCredentialManagementApp); - } else { - Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) - || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); - } + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) + || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization( isAliasInCredentialManagementAppPolicy(caller, alias), @@ -6802,13 +6627,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private boolean canInstallCertificates(CallerIdentity caller) { - if (isPermissionCheckFlagEnabled()) { - return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()); - } else { - return isProfileOwner(caller) || isDefaultDeviceOwner(caller) - || isCallerDelegate(caller, DELEGATION_CERT_INSTALL); - } + return isProfileOwner(caller) || isDefaultDeviceOwner(caller) + || isCallerDelegate(caller, DELEGATION_CERT_INSTALL); } private boolean canChooseCertificates(CallerIdentity caller) { @@ -7001,16 +6821,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { caller.getPackageName(), caller.getUid())); enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags); } else { - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()) - || isCredentialManagementApp); - } else { - Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner( - caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && ( - isCallerDelegate || isCredentialManagementApp))); - } + Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner( + caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && ( + isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization( isAliasInCredentialManagementAppPolicy(caller, alias), @@ -7143,16 +6956,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(who, callerPackage); final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL); final boolean isCredentialManagementApp = isCredentialManagementApp(caller); - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()) - || isCredentialManagementApp); - } else { - Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) - || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); - } + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) + || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization( isAliasInCredentialManagementAppPolicy(caller, alias), @@ -8285,29 +8091,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } - if (!isPermissionCheckFlagEnabled()) { - Preconditions.checkNotNull(who, "ComponentName is null"); - } + + Preconditions.checkNotNull(who, "ComponentName is null"); + CallerIdentity caller = getCallerIdentity(who, callerPackageName); - if (!isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); + checkCanExecuteOrThrowUnsafe(DevicePolicyManager .OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY); final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow(); synchronized (getLockObject()) { ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - admin = enforcePermissionAndGetEnforcingAdmin( - who, MANAGE_DEVICE_POLICY_FACTORY_RESET, caller.getPackageName(), - UserHandle.USER_ALL) - .getActiveAdmin(); - } else { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); admin.mFactoryResetProtectionPolicy = policy; saveSettingsLocked(caller.getUserId()); } @@ -8347,7 +8145,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || hasCallingPermission(permission.MASTER_CLEAR) || hasCallingPermission(MANAGE_DEVICE_POLICY_FACTORY_RESET), "Must be called by the FRP management agent on device"); - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked(); + admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); } else { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) @@ -10247,15 +10045,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return admin; } - ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked() { - ensureLocked(); - ActiveAdmin doOrPo = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - if (isPermissionCheckFlagEnabled() && doOrPo == null) { - return getUserData(0).mPermissionBasedAdmin; - } - return doOrPo; - } - @Override public void clearDeviceOwner(String packageName) { Objects.requireNonNull(packageName, "packageName is null"); @@ -10998,8 +10787,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * (2.1.1) The caller is the profile owner. * (2.1.2) The caller is from another app in the same user as the profile owner, AND * the caller is the delegated cert installer. - * (3) The caller holds the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission. * * For the device owner case, simply check that the caller is the device owner or the * delegated certificate installer. @@ -11013,24 +10800,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @VisibleForTesting boolean hasDeviceIdAccessUnchecked(String packageName, int uid) { final int userId = UserHandle.getUserId(uid); - // TODO(b/280048070): Introduce a permission to handle device ID access - if (isPermissionCheckFlagEnabled() - && !(isUidProfileOwnerLocked(uid) || isUidDeviceOwnerLocked(uid))) { - return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, packageName, userId); - } else { - ComponentName deviceOwner = getDeviceOwnerComponent(true); - if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName) - || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) { - return true; - } - ComponentName profileOwner = getProfileOwnerAsUser(userId); - final boolean isCallerProfileOwnerOrDelegate = profileOwner != null - && (profileOwner.getPackageName().equals(packageName) - || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL)); - if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId) - || isUserAffiliatedWithDevice(userId))) { - return true; - } + ComponentName deviceOwner = getDeviceOwnerComponent(true); + if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName) + || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) { + return true; + } + ComponentName profileOwner = getProfileOwnerAsUser(userId); + final boolean isCallerProfileOwnerOrDelegate = profileOwner != null + && (profileOwner.getPackageName().equals(packageName) + || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL)); + if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId) + || isUserAffiliatedWithDevice(userId))) { + return true; } return false; } @@ -11731,25 +11512,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setDefaultSmsApplication(ComponentName admin, String callerPackageName, String packageName, boolean parent) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - } else { - caller = getCallerIdentity(admin); - } + CallerIdentity caller = getCallerIdentity(admin); - final int userId; - if (isPermissionCheckFlagEnabled()) { - enforcePermission( - MANAGE_DEVICE_POLICY_DEFAULT_SMS, - caller.getPackageName(), - getAffectedUser(parent)); - } else { - Objects.requireNonNull(admin, "ComponentName is null"); - Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + Objects.requireNonNull(admin, "ComponentName is null"); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); if (!parent && isManagedProfile(caller.getUserId()) && getManagedSubscriptionsPolicy().getPolicyType() @@ -11759,6 +11527,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + "ManagedSubscriptions policy is set"); } + final int userId; if (parent) { userId = getProfileParentId(mInjector.userHandleGetCallingUserId()); mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage( @@ -11957,10 +11726,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(admin, "admin is null"); - } - + Objects.requireNonNull(admin, "admin is null"); Objects.requireNonNull(agent, "agent is null"); PolicySizeVerifier.enforceMaxPackageNameLength(agent.getPackageName()); @@ -11972,19 +11738,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int userHandle = mInjector.userHandleGetCallingUserId(); synchronized (getLockObject()) { - ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - CallerIdentity caller = getCallerIdentity(admin, callerPackageName); - int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; - ap = enforcePermissionAndGetEnforcingAdmin( - admin, - /*permission=*/MANAGE_DEVICE_POLICY_KEYGUARD, - /*adminPolicy=*/DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, - caller.getPackageName(), affectedUserId).getActiveAdmin(); - } else { - ap = getActiveAdminForCallerLocked(admin, - DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent); - } + ActiveAdmin ap = getActiveAdminForCallerLocked(admin, + DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent); checkCanExecuteOrThrowUnsafe( DevicePolicyManager.OPERATION_SET_TRUST_AGENT_CONFIGURATION); @@ -12080,27 +11835,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void addCrossProfileIntentFilter(ComponentName who, String callerPackageName, IntentFilter filter, int flags) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - } else { - caller = getCallerIdentity(who); - } - int callingUserId = caller.getUserId(); + CallerIdentity caller = getCallerIdentity(who); + + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); - if (isPermissionCheckFlagEnabled()) { - enforcePermission( - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - callingUserId); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isProfileOwner(caller) || isDefaultDeviceOwner(caller)); - } synchronized (getLockObject()) { long id = mInjector.binderClearCallingIdentity(); try { + int callingUserId = caller.getUserId(); UserInfo parent = mUserManager.getProfileParent(callingUserId); if (parent == null) { Slogf.e(LOG_TAG, "Cannot call addCrossProfileIntentFilter if there is no " @@ -12144,28 +11888,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void clearCrossProfileIntentFilters(ComponentName who, String callerPackageName) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - } else { - caller = getCallerIdentity(who); - } - int callingUserId = caller.getUserId(); + CallerIdentity caller = getCallerIdentity(who); - if (isPermissionCheckFlagEnabled()) { - enforcePermission( - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - callingUserId); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isProfileOwner(caller) || isDefaultDeviceOwner(caller)); - } + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { long id = mInjector.binderClearCallingIdentity(); try { + int callingUserId = caller.getUserId(); UserInfo parent = mUserManager.getProfileParent(callingUserId); if (parent == null) { Slogf.e(LOG_TAG, "Cannot call clearCrossProfileIntentFilter if there is no " @@ -15166,19 +14898,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - enforcePermission(MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(), - UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(who); - Preconditions.checkNotNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + CallerIdentity caller = getCallerIdentity(who); + Preconditions.checkNotNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, @@ -15197,16 +14922,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } CallerIdentity caller = getCallerIdentity(who); - if (isPermissionCheckFlagEnabled()) { - enforcePermission(MANAGE_DEVICE_POLICY_WIFI, who.getPackageName(), - UserHandle.USER_ALL); - } else { - Preconditions.checkNotNull(who, "ComponentName is null"); - - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + Preconditions.checkNotNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); return mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0); @@ -15294,18 +15013,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean setTime(@Nullable ComponentName who, String callerPackageName, long millis) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - // This is a global action. - enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); // Don't allow set time when auto time is on. if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) { @@ -15322,18 +15034,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean setTimeZone(@Nullable ComponentName who, String callerPackageName, String timeZone) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - // This is a global action. - enforcePermission(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); // Don't allow set timezone when auto timezone is on. if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) { @@ -16537,22 +16242,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.validateAgainstPreviousFreezePeriod(record.first, record.second, LocalDate.now()); } - CallerIdentity caller; - synchronized (getLockObject()) { - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(), - UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(caller) + CallerIdentity caller = getCallerIdentity(who); + Preconditions.checkCallAuthorization( + isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller)); - } - checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY); + checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY); + synchronized (getLockObject()) { if (policy == null) { mOwners.clearSystemUpdatePolicy(); } else { @@ -16699,7 +16397,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mUserManager.getUserInfo(UserHandle.getCallingUserId()).isMain()) { Slogf.w(LOG_TAG, "Only the system update service in the main user can broadcast " + "update information."); - return; } }); @@ -16723,7 +16420,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } // Get running users. - final int runningUserIds[]; + final int[] runningUserIds; try { runningUserIds = mInjector.getIActivityManager().getRunningUserIds(); } catch (RemoteException e) { @@ -16966,10 +16663,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } } - if (!isRuntimePermission(permission)) { - return false; - } - return true; + return isRuntimePermission(permission); } private void enforcePermissionGrantStateOnFinancedDevice( @@ -17384,18 +17078,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public String getWifiMacAddress(ComponentName admin, String callerPackageName) { -// if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(admin, "ComponentName is null"); -// } + Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin, callerPackageName); -// if (isPermissionCheckFlagEnabled()) { -// enforcePermission(MANAGE_DEVICE_POLICY_WIFI, UserHandle.USER_ALL); -// } else { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); -// } + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); return mInjector.binderWithCleanCallingIdentity(() -> { String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses(); @@ -17462,25 +17150,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } - CallerIdentity caller; - ActiveAdmin admin; message = PolicySizeVerifier.truncateIfLonger(message, MAX_SHORT_SUPPORT_MESSAGE_LENGTH); - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - synchronized (getLockObject()) { - admin = getActiveAdminForUidLocked(who, caller.getUid()); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + + ActiveAdmin admin; + synchronized (getLockObject()) { + admin = getActiveAdminForUidLocked(who, caller.getUid()); } synchronized (getLockObject()) { @@ -17501,23 +17179,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return null; } - CallerIdentity caller; - ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - synchronized (getLockObject()) { - admin = getActiveAdminForUidLocked(who, caller.getUid()); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + + ActiveAdmin admin; + synchronized (getLockObject()) { + admin = getActiveAdminForUidLocked(who, caller.getUid()); } return admin.shortSupportMessage; } @@ -17680,26 +17348,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } CallerIdentity caller = getCallerIdentity(who); - ActiveAdmin admin = null; - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); - } + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); text = PolicySizeVerifier.truncateIfLonger(text, MAX_ORG_NAME_LENGTH); synchronized (getLockObject()) { - if (!isPermissionCheckFlagEnabled()) { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); if (!TextUtils.equals(admin.organizationName, text)) { admin.organizationName = (text == null || text.length() == 0) ? null : text.toString(); @@ -17714,23 +17370,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return null; } CallerIdentity caller = getCallerIdentity(who); - ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallingUser(isManagedProfile(caller.getUserId())); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallingUser(isManagedProfile(caller.getUserId())); + Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); - synchronized (getLockObject()) { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + ActiveAdmin admin; + synchronized (getLockObject()) { + admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); } return admin.organizationName; @@ -18214,28 +17861,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final CallerIdentity caller = getCallerIdentity(admin, packageName); - if (isPermissionCheckFlagEnabled()) { - synchronized (getLockObject()) { - Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() - || areAllUsersAffiliatedWithDeviceLocked()); - enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(), - UserHandle.USER_ALL); - } + if (admin != null) { + Preconditions.checkCallAuthorization( + isProfileOwnerOfOrganizationOwnedDevice(caller) + || isDefaultDeviceOwner(caller)); } else { - if (admin != null) { - Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(caller) - || isDefaultDeviceOwner(caller)); - } else { - // A delegate app passes a null admin component, which is expected - Preconditions.checkCallAuthorization( - isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING)); - } + // A delegate app passes a null admin component, which is expected + Preconditions.checkCallAuthorization( + isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING)); + } - synchronized (getLockObject()) { - Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() - || areAllUsersAffiliatedWithDeviceLocked()); - } + synchronized (getLockObject()) { + Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() + || areAllUsersAffiliatedWithDeviceLocked()); } DevicePolicyEventLogger @@ -18259,7 +17897,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return new ParceledListSlice<SecurityEvent>(output); } catch (IOException e) { Slogf.w(LOG_TAG, "Fail to read previous events" , e); - return new ParceledListSlice<SecurityEvent>(Collections.<SecurityEvent>emptyList()); + return new ParceledListSlice<SecurityEvent>(Collections.emptyList()); } } @@ -18752,8 +18390,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private boolean hasIncompatibleAccounts(int userId) { - return mHasIncompatibleAccounts == null ? true - : mHasIncompatibleAccounts.getOrDefault(userId, /* default= */ false); + return mHasIncompatibleAccounts == null || mHasIncompatibleAccounts.getOrDefault( + userId, /* default= */ false); } /** @@ -18870,7 +18508,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } } - }; + } private boolean isAdb(CallerIdentity caller) { return isShellUid(caller) || isRootUid(caller); @@ -20168,21 +19806,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void installUpdateFromFile(ComponentName admin, String callerPackageName, ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback) { - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(admin, "ComponentName is null"); - } + Objects.requireNonNull(admin, "ComponentName is null"); - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(), - UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + CallerIdentity caller = getCallerIdentity(admin); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE); DevicePolicyEventLogger @@ -20752,32 +20381,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setCommonCriteriaModeEnabled(ComponentName who, String callerPackageName, boolean enabled) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - } else { - caller = getCallerIdentity(who); - } - final ActiveAdmin admin; + CallerIdentity caller = getCallerIdentity(who); - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), - "Common Criteria mode can only be controlled by a device owner or " - + "a profile owner on an organization-owned device."); - synchronized (getLockObject()) { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } - } + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "Common Criteria mode can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); synchronized (getLockObject()) { + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); admin.mCommonCriteriaMode = enabled; saveSettingsLocked(caller.getUserId()); } @@ -20809,7 +20421,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // their ActiveAdmin, instead of iterating through all admins. ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - return admin != null ? admin.mCommonCriteriaMode : false; + return admin != null && admin.mCommonCriteriaMode; } } @@ -22209,7 +21821,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else { owner = getDeviceOrProfileOwnerAdminLocked(userId); } - boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false; + boolean canGrant = owner != null && owner.mAdminCanGrantSensorsPermissions; mPolicyCache.setAdminCanGrantSensorsPermissions(canGrant); } } @@ -22408,27 +22020,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setMinimumRequiredWifiSecurityLevel(String callerPackageName, int level) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(callerPackageName); - } else { - caller = getCallerIdentity(); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), - "Wi-Fi minimum security level can only be controlled by a device owner or " - + "a profile owner on an organization-owned device."); - } + CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "Wi-Fi minimum security level can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); boolean valueChanged = false; synchronized (getLockObject()) { - ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - admin = enforcePermissionAndGetEnforcingAdmin(/* admin= */ null, - MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(), caller.getUserId()) - .getActiveAdmin(); - } else { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); if (admin.mWifiMinimumSecurityLevel != level) { admin.mWifiMinimumSecurityLevel = level; saveSettingsLocked(caller.getUserId()); @@ -22450,21 +22050,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public WifiSsidPolicy getWifiSsidPolicy(String callerPackageName) { final CallerIdentity caller = getCallerIdentity(); - if (isPermissionCheckFlagEnabled()) { - enforcePermission(MANAGE_DEVICE_POLICY_WIFI, callerPackageName, - caller.getUserId()); - } else { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller) - || canQueryAdminPolicy(caller), - "SSID policy can only be retrieved by a device owner or " - + "a profile owner on an organization-owned device or " - + "an app with the QUERY_ADMIN_POLICY permission."); - } + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller) + || canQueryAdminPolicy(caller), + "SSID policy can only be retrieved by a device owner or " + + "a profile owner on an organization-owned device or " + + "an app with the QUERY_ADMIN_POLICY permission."); synchronized (getLockObject()) { ActiveAdmin admin; - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked(); + admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); return admin != null ? admin.mWifiSsidPolicy : null; } } @@ -22485,29 +22080,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setWifiSsidPolicy(String callerPackageName, WifiSsidPolicy policy) { - CallerIdentity caller; - - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(callerPackageName); - } else { - caller = getCallerIdentity(); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), - "SSID denylist can only be controlled by a device owner or " - + "a profile owner on an organization-owned device."); - } + CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "SSID denylist can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); boolean changed = false; synchronized (getLockObject()) { - ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - admin = enforcePermissionAndGetEnforcingAdmin( - /* admin= */ null, MANAGE_DEVICE_POLICY_WIFI, - caller.getPackageName(), - caller.getUserId()).getActiveAdmin(); - } else { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); if (!Objects.equals(policy, admin.mWifiSsidPolicy)) { admin.mWifiSsidPolicy = policy; changed = true; @@ -22715,7 +22296,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private final class DevicePolicyManagementRoleObserver implements OnRoleHoldersChangedListener { - private RoleManager mRm; + private final RoleManager mRm; private final Executor mExecutor; private final Context mContext; @@ -22732,13 +22313,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { mDevicePolicyEngine.handleRoleChanged(roleName, user.getIdentifier()); - if (RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) { - handleDevicePolicyManagementRoleChange(user); - return; - } - if (RoleManager.ROLE_FINANCED_DEVICE_KIOSK.equals(roleName)) { - handleFinancedDeviceKioskRoleChange(); - return; + switch (roleName) { + case RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT -> + handleDevicePolicyManagementRoleChange(user); + case RoleManager.ROLE_FINANCED_DEVICE_KIOSK -> + handleFinancedDeviceKioskRoleChange(); } } @@ -23390,26 +22969,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Checks if the calling process has been granted permission to apply a device policy on a - * specific user. - * The given permission will be checked along with its associated cross-user permission if it - * exists and the target user is different to the calling user. - * Returns an {@link EnforcingAdmin} for the caller. - * - * @param admin the component name of the admin. - * @param callerPackageName The package name of the calling application. - * @param permission The name of the permission being checked. - * @param deviceAdminPolicy The userId of the user which the caller needs permission to act on. - * @throws SecurityException if the caller has not been granted the given permission, - * the associated cross-user permission if the caller's user is different to the target user. - */ - private EnforcingAdmin enforcePermissionAndGetEnforcingAdmin(@Nullable ComponentName admin, - String permission, int deviceAdminPolicy, String callerPackageName, int targetUserId) { - enforcePermission(permission, deviceAdminPolicy, callerPackageName, targetUserId); - return getEnforcingAdminForCaller(admin, callerPackageName); - } - - /** - * Checks if the calling process has been granted permission to apply a device policy on a * specific user. Only one permission provided in the list needs to be granted to pass this * check. * The given permissions will be checked along with their associated cross-user permissions if @@ -23431,23 +22990,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** - * Checks whether the calling process has been granted permission to query a device policy on - * a specific user. - * The given permission will be checked along with its associated cross-user permission if it - * exists and the target user is different to the calling user. - * - * @param permission The name of the permission being checked. - * @param targetUserId The userId of the user which the caller needs permission to act on. - * @throws SecurityException if the caller has not been granted the given permission, - * the associated cross-user permission if the caller's user is different to the target user. - */ - private EnforcingAdmin enforceCanQueryAndGetEnforcingAdmin(@Nullable ComponentName admin, - String permission, String callerPackageName, int targetUserId) { - enforceCanQuery(permission, callerPackageName, targetUserId); - return getEnforcingAdminForCaller(admin, callerPackageName); - } - - /** * Checks if the calling process has been granted permission to apply a device policy. * * @param callerPackageName The package name of the calling application. @@ -23754,13 +23296,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return NOT_A_DPC; } - private boolean isPermissionCheckFlagEnabled() { - return DeviceConfig.getBoolean( - NAMESPACE_DEVICE_POLICY_MANAGER, - PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG, - DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG); - } - private static boolean isSetStatusBarDisabledCoexistenceEnabled() { return false; } @@ -23837,58 +23372,83 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); } - if (isPermissionCheckFlagEnabled()) { + if (Flags.setMtePolicyCoexistence()) { enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(), UserHandle.USER_ALL); } else { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); + || isProfileOwnerOfOrganizationOwnedDevice(caller)); } + synchronized (getLockObject()) { - ActiveAdmin admin = + if (Flags.setMtePolicyCoexistence()) { + final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null, + MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId()); + if (flags != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) { + mDevicePolicyEngine.setGlobalPolicy( + PolicyDefinition.MEMORY_TAGGING, + admin, + new IntegerPolicyValue(flags)); + } else { + mDevicePolicyEngine.removeGlobalPolicy( + PolicyDefinition.MEMORY_TAGGING, + admin); + } + } else { + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - - if (admin != null) { - final String memtagProperty = "arm64.memtag.bootctl"; - if (flags == DevicePolicyManager.MTE_ENABLED) { - mInjector.systemPropertiesSet(memtagProperty, "memtag"); - } else if (flags == DevicePolicyManager.MTE_DISABLED) { - mInjector.systemPropertiesSet(memtagProperty, "memtag-off"); - } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) { - if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) { - mInjector.systemPropertiesSet(memtagProperty, "default"); + if (admin != null) { + final String memtagProperty = "arm64.memtag.bootctl"; + if (flags == DevicePolicyManager.MTE_ENABLED) { + mInjector.systemPropertiesSet(memtagProperty, "memtag"); + } else if (flags == DevicePolicyManager.MTE_DISABLED) { + mInjector.systemPropertiesSet(memtagProperty, "memtag-off"); + } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) { + if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) { + mInjector.systemPropertiesSet(memtagProperty, "default"); + } } + admin.mtePolicy = flags; + saveSettingsLocked(caller.getUserId()); } - admin.mtePolicy = flags; - saveSettingsLocked(caller.getUserId()); - - DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY) - .setInt(flags) - .setAdmin(caller.getPackageName()) - .write(); } + + DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY) + .setInt(flags) + .setAdmin(caller.getPackageName()) + .write(); } } @Override public int getMtePolicy(String callerPackageName) { final CallerIdentity caller = getCallerIdentity(callerPackageName); - if (isPermissionCheckFlagEnabled()) { + if (Flags.setMtePolicyCoexistence()) { enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(), UserHandle.USER_ALL); } else { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller) - || isSystemUid(caller)); + || isProfileOwnerOfOrganizationOwnedDevice(caller) + || isSystemUid(caller)); } + synchronized (getLockObject()) { - ActiveAdmin admin = + if (Flags.setMtePolicyCoexistence()) { + final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null, + MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId()); + final Integer policyFromAdmin = mDevicePolicyEngine.getGlobalPolicySetByAdmin( + PolicyDefinition.MEMORY_TAGGING, admin); + return (policyFromAdmin != null ? policyFromAdmin + : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY); + } else { + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - return admin != null - ? admin.mtePolicy - : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY; + return admin != null + ? admin.mtePolicy + : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY; + } } } @@ -24250,6 +23810,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Slogf.i(LOG_TAG, "Backup made: " + supervisionBackupId); } + String memoryTaggingBackupId = "36.3.memory-tagging"; + boolean memoryTaggingMigrated = maybeMigrateMemoryTaggingLocked(memoryTaggingBackupId); + if (memoryTaggingMigrated) { + Slogf.i(LOG_TAG, "Backup made: " + memoryTaggingBackupId); + } + // Additional migration steps should repeat the pattern above with a new backupId. } @@ -24666,7 +24232,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || isCallerDevicePolicyManagementRoleHolder(caller) || isCallerSystemSupervisionRoleHolder(caller)); return getFinancedDeviceKioskRoleHolderOnAnyUser() != null; - }; + } @Override public String getFinancedDeviceKioskRoleHolder(String callerPackageName) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index b3c8408ff54b..be4eea42f09e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -682,6 +682,19 @@ class Owners { } } + void markMemoryTaggingMigrated() { + synchronized (mData) { + mData.mMemoryTaggingMigrated = true; + mData.writeDeviceOwner(); + } + } + + boolean isMemoryTaggingMigrated() { + synchronized (mData) { + return mData.mMemoryTaggingMigrated; + } + } + @GuardedBy("mData") void pushToAppOpsLocked() { if (!mSystemReady) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java index 10e43d955fab..952bbca58f3b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java @@ -93,6 +93,9 @@ class OwnersData { private static final String ATTR_SUSPENDED_PACKAGES_MIGRATED = "suspendedPackagesMigrated"; private static final String ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED = "resetPasswordWithTokenMigrated"; + private static final String ATTR_MEMORY_TAGGING_MIGRATED = + "memoryTaggingMigrated"; + private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade"; // Internal state for the device owner package. @@ -125,6 +128,7 @@ class OwnersData { boolean mRequiredPasswordComplexityMigrated = false; boolean mSuspendedPackagesMigrated = false; boolean mResetPasswordWithTokenMigrated = false; + boolean mMemoryTaggingMigrated = false; boolean mPoliciesMigratedPostUpdate = false; @@ -424,6 +428,10 @@ class OwnersData { out.attributeBoolean(null, ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED, mResetPasswordWithTokenMigrated); } + if (Flags.setMtePolicyCoexistence()) { + out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED, + mMemoryTaggingMigrated); + } out.endTag(null, TAG_POLICY_ENGINE_MIGRATION); } @@ -497,7 +505,9 @@ class OwnersData { mResetPasswordWithTokenMigrated = Flags.resetPasswordWithTokenCoexistence() && parser.getAttributeBoolean(null, ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED, false); - + mMemoryTaggingMigrated = Flags.setMtePolicyCoexistence() + && parser.getAttributeBoolean(null, + ATTR_MEMORY_TAGGING_MIGRATED, false); break; default: Slog.e(TAG, "Unexpected tag: " + tag); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 6cb1756f93eb..f1711f5f8c0b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -337,6 +337,13 @@ final class PolicyDefinition<V> { PolicyEnforcerCallbacks::noOp, new PackageSetPolicySerializer()); + static PolicyDefinition<Integer> MEMORY_TAGGING = new PolicyDefinition<>( + new NoArgsPolicyKey( + DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY), + new TopPriority<>(List.of(EnforcingAdmin.DPC_AUTHORITY)), + PolicyEnforcerCallbacks::setMtePolicy, + new IntegerPolicySerializer()); + private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>(); private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>(); @@ -383,6 +390,8 @@ final class PolicyDefinition<V> { PASSWORD_COMPLEXITY); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PACKAGES_SUSPENDED_POLICY, PACKAGES_SUSPENDED); + POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY, + MEMORY_TAGGING); // User Restriction Policies USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 145416215cb0..fdc0ec1a0471 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -47,6 +47,7 @@ import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.UserHandle; import android.permission.AdminPermissionControlParams; import android.permission.PermissionControllerManager; @@ -210,6 +211,7 @@ final class PolicyEnforcerCallbacks { private static class BlockingCallback { private final CountDownLatch mLatch = new CountDownLatch(1); private final AtomicReference<Boolean> mValue = new AtomicReference<>(); + public void trigger(Boolean value) { mValue.set(value); mLatch.countDown(); @@ -435,4 +437,43 @@ final class PolicyEnforcerCallbacks { return AndroidFuture.completedFuture(true); }); } + + static CompletableFuture<Boolean> setMtePolicy( + @Nullable Integer mtePolicy, @NonNull Context context, int userId, + @NonNull PolicyKey policyKey) { + if (mtePolicy == null) { + mtePolicy = DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY; + } + final Set<Integer> allowedModes = + Set.of( + DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY, + DevicePolicyManager.MTE_DISABLED, + DevicePolicyManager.MTE_ENABLED); + if (!allowedModes.contains(mtePolicy)) { + Slog.wtf(LOG_TAG, "MTE policy is not a known one: " + mtePolicy); + return AndroidFuture.completedFuture(false); + } + + final String mteDpmSystemProperty = + "ro.arm64.memtag.bootctl_device_policy_manager"; + final String mteSettingsSystemProperty = + "ro.arm64.memtag.bootctl_settings_toggle"; + final String mteControlProperty = "arm64.memtag.bootctl"; + + final boolean isAvailable = SystemProperties.getBoolean(mteDpmSystemProperty, + SystemProperties.getBoolean(mteSettingsSystemProperty, false)); + if (!isAvailable) { + return AndroidFuture.completedFuture(false); + } + + if (mtePolicy == DevicePolicyManager.MTE_ENABLED) { + SystemProperties.set(mteControlProperty, "memtag"); + } else if (mtePolicy == DevicePolicyManager.MTE_DISABLED) { + SystemProperties.set(mteControlProperty, "memtag-off"); + } else if (mtePolicy == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) { + SystemProperties.set(mteControlProperty, "default"); + } + + return AndroidFuture.completedFuture(true); + } } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java index a804f24acc8f..c30ab738b098 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java @@ -101,13 +101,13 @@ public final class InputMethodSubtypeSwitchingControllerTest { TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID, TEST_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, TEST_IS_VR_IME); if (subtypes == null) { - items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi, - NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE)); + items.add(new ImeSubtypeListItem(imeName, null /* subtypeName */, null /* layoutName */, + imi, NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE)); } else { for (int i = 0; i < subtypes.size(); ++i) { final String subtypeLocale = subtypeLocales.get(i); - items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale, - SYSTEM_LOCALE)); + items.add(new ImeSubtypeListItem(imeName, subtypeLocale, null /* layoutName */, + imi, i, subtypeLocale, SYSTEM_LOCALE)); } } } @@ -138,8 +138,8 @@ public final class InputMethodSubtypeSwitchingControllerTest { final InputMethodInfo imi = new InputMethodInfo(ri, TEST_IS_AUX_IME, TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID, TEST_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */, TEST_IS_VR_IME); - return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale, - SYSTEM_LOCALE); + return new ImeSubtypeListItem(imeName, subtypeName, null /* layoutName */, imi, + subtypeIndex, subtypeLocale, SYSTEM_LOCALE); } @NonNull diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java index 3ac7fb0dbe53..e0b0fec380dd 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java @@ -335,6 +335,10 @@ public class VirtualDisplayAdapterTest { @Override public void onStopped() { } + + @Override + public void onRequestedBrightnessChanged(float brightness) { + } }; } } diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index c81d6be43223..9a300fb3c9fd 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. java_defaults { - name: "FrameworkMockingServicesTests-jni-defaults", + name: "FrameworksMockingServicesTests-jni-defaults", jni_libs: [ "libmockingservicestestjni", ], @@ -30,7 +30,7 @@ package { android_test { name: "FrameworksMockingServicesTests", defaults: [ - "FrameworkMockingServicesTests-jni-defaults", + "FrameworksMockingServicesTests-jni-defaults", "modules-utils-testable-device-config-defaults", ], diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index cbc8538cf9fb..37d1c30f76f5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -15,7 +15,12 @@ */ package com.android.server; +import static android.os.PowerExemptionManager.REASON_OTHER; +import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT; + import static androidx.test.InstrumentationRegistry.getContext; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -31,6 +36,7 @@ import static com.android.server.DeviceIdleController.LIGHT_STATE_INACTIVE; import static com.android.server.DeviceIdleController.LIGHT_STATE_OVERRIDE; import static com.android.server.DeviceIdleController.LIGHT_STATE_WAITING_FOR_NETWORK; import static com.android.server.DeviceIdleController.MSG_REPORT_STATIONARY_STATUS; +import static com.android.server.DeviceIdleController.MSG_TEMP_APP_WHITELIST_TIMEOUT; import static com.android.server.DeviceIdleController.STATE_ACTIVE; import static com.android.server.DeviceIdleController.STATE_IDLE; import static com.android.server.DeviceIdleController.STATE_IDLE_MAINTENANCE; @@ -41,6 +47,7 @@ import static com.android.server.DeviceIdleController.STATE_QUICK_DOZE_DELAY; import static com.android.server.DeviceIdleController.STATE_SENSING; import static com.android.server.DeviceIdleController.lightStateToString; import static com.android.server.DeviceIdleController.stateToString; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -83,6 +90,8 @@ import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.SystemClock; import android.os.WearModeManagerInternal; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; @@ -90,12 +99,16 @@ import android.telephony.emergency.EmergencyNumber; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.app.IBatteryStats; +import com.android.server.am.BatteryStatsService; import com.android.server.deviceidle.ConstraintController; +import com.android.server.deviceidle.Flags; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -115,6 +128,9 @@ import java.util.concurrent.Executor; @SuppressWarnings("GuardedBy") @RunWith(AndroidJUnit4.class) public class DeviceIdleControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT); + private DeviceIdleController mDeviceIdleController; private DeviceIdleController.MyHandler mHandler; private AnyMotionDetectorForTest mAnyMotionDetector; @@ -157,7 +173,8 @@ public class DeviceIdleControllerTest { LocationManager locationManager; ConstraintController constraintController; // Freeze time for testing. - long nowElapsed; + volatile long nowElapsed; + volatile long nowUptime; boolean useMotionSensor = true; boolean isLocationPrefetchEnabled = true; @@ -193,6 +210,11 @@ public class DeviceIdleControllerTest { } @Override + long getUptimeMillis() { + return nowUptime; + } + + @Override LocationManager getLocationManager() { return locationManager; } @@ -314,11 +336,13 @@ public class DeviceIdleControllerTest { mMockingSession = mockitoSession() .initMocks(this) .strictness(Strictness.LENIENT) + .mockStatic(BatteryStatsService.class) .spyStatic(DeviceConfig.class) .spyStatic(LocalServices.class) .startMocking(); spyOn(getContext()); doReturn(null).when(getContext()).registerReceiver(any(), any()); + doReturn(mock(IBatteryStats.class)).when(() -> BatteryStatsService.getService()); doReturn(mock(ActivityManagerInternal.class)) .when(() -> LocalServices.getService(ActivityManagerInternal.class)); doReturn(mock(ActivityTaskManagerInternal.class)) @@ -401,6 +425,46 @@ public class DeviceIdleControllerTest { } @Test + @EnableFlags(Flags.FLAG_USE_CPU_TIME_FOR_TEMP_ALLOWLIST) + public void testTempAllowlistCountsUptime() { + doNothing().when(getContext()).sendBroadcastAsUser(any(), any(), any(), any()); + final int testUid = 12345; + final long durationMs = 4300; + final long startTime = 100; // Arbitrary starting point in time. + mInjector.nowUptime = mInjector.nowElapsed = startTime; + + mDeviceIdleController.addPowerSaveTempWhitelistAppDirectInternal(0, testUid, durationMs, + TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, true, REASON_OTHER, "test"); + + assertEquals(startTime + durationMs, + mDeviceIdleController.mTempWhitelistAppIdEndTimes.get(testUid).first.value); + + final InOrder inorder = inOrder(mHandler); + // mHandler is already stubbed to do nothing on handleMessage. + inorder.verify(mHandler).sendMessageDelayed( + argThat(m -> m.what == MSG_TEMP_APP_WHITELIST_TIMEOUT && m.arg1 == testUid), + eq(durationMs)); + + mInjector.nowElapsed += durationMs; + mInjector.nowUptime += 2; + // Elapsed time moved past the expiration but not uptime. The check should be rescheduled. + mDeviceIdleController.checkTempAppWhitelistTimeout(testUid); + inorder.verify(mHandler).sendMessageDelayed( + argThat(m -> m.what == MSG_TEMP_APP_WHITELIST_TIMEOUT && m.arg1 == testUid), + eq(durationMs - 2)); + assertEquals(startTime + durationMs, + mDeviceIdleController.mTempWhitelistAppIdEndTimes.get(testUid).first.value); + + mInjector.nowUptime += durationMs; + // Uptime moved past the expiration time. Uid should be removed from the temp allowlist. + mDeviceIdleController.checkTempAppWhitelistTimeout(testUid); + inorder.verify(mHandler, never()).sendMessageDelayed( + argThat(m -> m.what == MSG_TEMP_APP_WHITELIST_TIMEOUT && m.arg1 == testUid), + anyLong()); + assertFalse(mDeviceIdleController.mTempWhitelistAppIdEndTimes.contains(testUid)); + } + + @Test public void testUpdateInteractivityLocked() { doReturn(false).when(mPowerManager).isInteractive(); mDeviceIdleController.updateInteractivityLocked(); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 4a1315583ad4..f82a86092064 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -109,6 +109,7 @@ import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import com.android.server.LocalServices; @@ -175,6 +176,7 @@ public class MockingOomAdjusterTests { private ActiveUids mActiveUids; private PackageManagerInternal mPackageManagerInternal; private ActivityManagerService mService; + private TestCachedAppOptimizer mTestCachedAppOptimizer; private OomAdjusterInjector mInjector = new OomAdjusterInjector(); private int mUiTierSize; @@ -242,9 +244,11 @@ public class MockingOomAdjusterTests { doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(), anyBoolean()); mActiveUids = new ActiveUids(mService, false); + mTestCachedAppOptimizer = new TestCachedAppOptimizer(mService); mProcessStateController = new ProcessStateController.Builder(mService, mService.mProcessList, mActiveUids) .useModernOomAdjuster(mService.mConstants.ENABLE_NEW_OOMADJ) + .setCachedAppOptimizer(mTestCachedAppOptimizer) .setOomAdjusterInjector(mInjector) .build(); mService.mProcessStateController = mProcessStateController; @@ -3110,13 +3114,13 @@ public class MockingOomAdjusterTests { mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, true); assertEquals(true, app.getUidRecord().isSetAllowListed()); - assertEquals(true, app.mOptRecord.shouldNotFreeze()); - assertEquals(true, app2.mOptRecord.shouldNotFreeze()); + assertFreezeState(app, false); + assertFreezeState(app2, false); mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false); assertEquals(false, app.getUidRecord().isSetAllowListed()); - assertEquals(false, app.mOptRecord.shouldNotFreeze()); - assertEquals(false, app2.mOptRecord.shouldNotFreeze()); + assertFreezeState(app, true); + assertFreezeState(app2, true); } @SuppressWarnings("GuardedBy") @@ -3138,25 +3142,25 @@ public class MockingOomAdjusterTests { assertEquals(true, app.getUidRecord().isSetAllowListed()); assertEquals(true, app2.getUidRecord().isSetAllowListed()); - assertEquals(true, app.mOptRecord.shouldNotFreeze()); - assertEquals(true, app2.mOptRecord.shouldNotFreeze()); - assertEquals(true, app3.mOptRecord.shouldNotFreeze()); + assertFreezeState(app, false); + assertFreezeState(app2, false); + assertFreezeState(app3, false); // Remove app1 from allowlist. mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false); assertEquals(false, app.getUidRecord().isSetAllowListed()); assertEquals(true, app2.getUidRecord().isSetAllowListed()); - assertEquals(false, app.mOptRecord.shouldNotFreeze()); - assertEquals(true, app2.mOptRecord.shouldNotFreeze()); - assertEquals(true, app3.mOptRecord.shouldNotFreeze()); + assertFreezeState(app, true); + assertFreezeState(app2, false); + assertFreezeState(app3, false); // Now remove app2 from allowlist. mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP2_UID, false); assertEquals(false, app.getUidRecord().isSetAllowListed()); assertEquals(false, app2.getUidRecord().isSetAllowListed()); - assertEquals(false, app.mOptRecord.shouldNotFreeze()); - assertEquals(false, app2.mOptRecord.shouldNotFreeze()); - assertEquals(false, app3.mOptRecord.shouldNotFreeze()); + assertFreezeState(app, true); + assertFreezeState(app2, true); + assertFreezeState(app3, true); } @SuppressWarnings("GuardedBy") @@ -3370,6 +3374,14 @@ public class MockingOomAdjusterTests { assertEquals(expectedCached, state.isCached()); } + @SuppressWarnings("GuardedBy") + private void assertFreezeState(ProcessRecord app, boolean expectedFreezeState) { + boolean actualFreezeState = mTestCachedAppOptimizer.mLastSetFreezeState.get(app.getPid(), + false); + assertEquals("Unexcepted freeze state for " + app.processName, expectedFreezeState, + actualFreezeState); + } + private class ProcessRecordBuilder { @SuppressWarnings("UnusedVariable") int mPid; @@ -3513,6 +3525,39 @@ public class MockingOomAdjusterTests { return app; } } + private static final class TestProcessDependencies + implements CachedAppOptimizer.ProcessDependencies { + @Override + public long[] getRss(int pid) { + return new long[]{/*totalRSS*/ 0, /*fileRSS*/ 0, /*anonRSS*/ 0, /*swap*/ 0}; + } + + @Override + public void performCompaction(CachedAppOptimizer.CompactProfile action, int pid) {} + } + + private static class TestCachedAppOptimizer extends CachedAppOptimizer { + private SparseBooleanArray mLastSetFreezeState = new SparseBooleanArray(); + + TestCachedAppOptimizer(ActivityManagerService ams) { + super(ams, null, new TestProcessDependencies()); + } + + @Override + public boolean useFreezer() { + return true; + } + + @Override + public void freezeAppAsyncLSP(ProcessRecord app) { + mLastSetFreezeState.put(app.getPid(), true); + } + + @Override + public void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) { + mLastSetFreezeState.put(app.getPid(), false); + } + } static class OomAdjusterInjector extends OomAdjuster.Injector { // Jump ahead in time by this offset amount. @@ -3524,7 +3569,6 @@ public class MockingOomAdjusterTests { mLastSetOomAdj.clear(); } - void jumpUptimeAheadTo(long uptimeMillis) { final long jumpMs = uptimeMillis - getUptimeMillis(); if (jumpMs <= 0) return; diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java index 197342874b2a..f7c2e8b72d6b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java @@ -21,6 +21,7 @@ import static android.app.AppOpsManager.OP_FINE_LOCATION; import static org.junit.Assert.assertEquals; +import android.app.PropertyInvalidatedCache; import android.content.Context; import android.os.Handler; @@ -63,6 +64,7 @@ public class AppOpsLegacyRestrictionsTest { @Before public void setUp() { + PropertyInvalidatedCache.disableForTestMode(); mSession = ExtendedMockito.mockitoSession() .initMocks(this) .strictness(Strictness.LENIENT) diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java index 4e1f741b1398..dd7ce21e3628 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -2351,6 +2351,7 @@ public class JobSchedulerServiceTest { /** Tests that jobs are removed from the pending list if the user stops the app. */ @Test + @RequiresFlagsDisabled(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API) public void testUserStopRemovesPending() { spyOn(mService); @@ -2402,6 +2403,60 @@ public class JobSchedulerServiceTest { assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b)); } + /** Tests that jobs are removed from the pending list if the user stops the app. */ + @Test + @RequiresFlagsEnabled(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API) + public void testUserStopRemovesPending_withPendingJobReasonsApi() { + spyOn(mService); + + JobStatus job1a = createJobStatus("testUserStopRemovesPending", + createJobInfo(1), 1, "pkg1"); + JobStatus job1b = createJobStatus("testUserStopRemovesPending", + createJobInfo(2), 1, "pkg1"); + JobStatus job2a = createJobStatus("testUserStopRemovesPending", + createJobInfo(1), 2, "pkg2"); + JobStatus job2b = createJobStatus("testUserStopRemovesPending", + createJobInfo(2), 2, "pkg2"); + doReturn(1).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 0); + doReturn(11).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 1); + doReturn(2).when(mPackageManagerInternal).getPackageUid("pkg2", 0, 0); + + mService.getPendingJobQueue().clear(); + mService.getPendingJobQueue().add(job1a); + mService.getPendingJobQueue().add(job1b); + mService.getPendingJobQueue().add(job2a); + mService.getPendingJobQueue().add(job2b); + mService.getJobStore().add(job1a); + mService.getJobStore().add(job1b); + mService.getJobStore().add(job2a); + mService.getJobStore().add(job2b); + + mService.notePendingUserRequestedAppStopInternal("pkg1", 1, "test"); + assertEquals(4, mService.getPendingJobQueue().size()); + assertTrue(mService.getPendingJobQueue().contains(job1a)); + assertTrue(mService.getPendingJobQueue().contains(job1b)); + assertTrue(mService.getPendingJobQueue().contains(job2a)); + assertTrue(mService.getPendingJobQueue().contains(job2b)); + + mService.notePendingUserRequestedAppStopInternal("pkg1", 0, "test"); + assertEquals(2, mService.getPendingJobQueue().size()); + assertFalse(mService.getPendingJobQueue().contains(job1a)); + assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job1a)[0]); + assertFalse(mService.getPendingJobQueue().contains(job1b)); + assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job1b)[0]); + assertTrue(mService.getPendingJobQueue().contains(job2a)); + assertTrue(mService.getPendingJobQueue().contains(job2b)); + + mService.notePendingUserRequestedAppStopInternal("pkg2", 0, "test"); + assertEquals(0, mService.getPendingJobQueue().size()); + assertFalse(mService.getPendingJobQueue().contains(job1a)); + assertFalse(mService.getPendingJobQueue().contains(job1b)); + assertFalse(mService.getPendingJobQueue().contains(job2a)); + assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job2a)[0]); + assertFalse(mService.getPendingJobQueue().contains(job2b)); + assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job2b)[0]); + } + /** * Unit tests {@link JobSchedulerService#checkIfRestricted(JobStatus)} with single {@link * JobRestriction} registered. diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index 1cba3c574543..8a10040f986f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -195,12 +195,12 @@ public final class UserManagerServiceTest { doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any()); mockIsLowRamDevice(false); - // Called when getting boot user. config_bootToHeadlessSystemUser is false by default. + // Called when getting boot user. config_bootToHeadlessSystemUser is 0 by default. mSpyResources = spy(mSpiedContext.getResources()); when(mSpiedContext.getResources()).thenReturn(mSpyResources); - doReturn(false) + doReturn(0) .when(mSpyResources) - .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser); + .getInteger(com.android.internal.R.integer.config_hsumBootStrategy); // Must construct UserManagerService in the UiThread mTestDir = new File(mRealContext.getDataDir(), "umstest"); @@ -859,15 +859,50 @@ public final class UserManagerServiceTest { } @Test - public void testGetBootUser_enableBootToHeadlessSystemUser() { + public void testGetBootUser_Headless_BootToSystemUserWhenDeviceIsProvisioned() { setSystemUserHeadless(true); - doReturn(true) + addUser(USER_ID); + addUser(OTHER_USER_ID); + mockProvisionedDevice(true); + doReturn(1) .when(mSpyResources) - .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser); + .getInteger(com.android.internal.R.integer.config_hsumBootStrategy); assertThat(mUms.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM); } + @Test + public void testGetBootUser_Headless_BootToFirstSwitchableFullUserWhenDeviceNotProvisioned() { + setSystemUserHeadless(true); + addUser(USER_ID); + addUser(OTHER_USER_ID); + mockProvisionedDevice(false); + doReturn(1) + .when(mSpyResources) + .getInteger(com.android.internal.R.integer.config_hsumBootStrategy); + // Even if the headless system user switchable flag is true, the boot user should be the + // first switchable full user. + doReturn(true) + .when(mSpyResources) + .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser); + + assertThat(mUms.getBootUser()).isEqualTo(USER_ID); + } + + @Test + public void testGetBootUser_Headless_ThrowsIfBootFailsNoFullUserWhenDeviceNotProvisioned() + throws Exception { + setSystemUserHeadless(true); + removeNonSystemUsers(); + mockProvisionedDevice(false); + doReturn(1) + .when(mSpyResources) + .getInteger(com.android.internal.R.integer.config_hsumBootStrategy); + + assertThrows(ServiceSpecificException.class, + () -> mUms.getBootUser()); + } + /** * Returns true if the user's XML file has Default restrictions * @param userId Id of the user. @@ -935,6 +970,11 @@ public final class UserManagerServiceTest { any(), eq(android.provider.Settings.Global.USER_SWITCHER_ENABLED), anyInt())); } + private void mockProvisionedDevice(boolean isProvisionedDevice) { + doReturn(isProvisionedDevice ? 1 : 0).when(() -> Settings.Global.getInt( + any(), eq(android.provider.Settings.Global.DEVICE_PROVISIONED), anyInt())); + } + private void mockIsLowRamDevice(boolean isLowRamDevice) { doReturn(isLowRamDevice).when(ActivityManager::isLowRamDeviceStatic); } diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java index c4b3c149bd8d..5d7ffe91e67d 100644 --- a/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java @@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import android.app.AppOpsManager; +import android.app.PropertyInvalidatedCache; import android.companion.virtual.VirtualDeviceManager; import android.content.Context; import android.os.FileUtils; @@ -69,6 +70,7 @@ public class AppOpsRecentAccessPersistenceTest { @Before public void setUp() { + PropertyInvalidatedCache.disableForTestMode(); when(mAppOpCheckingService.addAppOpsModeChangedListener(any())).thenReturn(true); LocalServices.addService(AppOpsCheckingServiceInterface.class, mAppOpCheckingService); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index b5724b5c0cc8..48bc9d7c51a1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -21,10 +21,8 @@ import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; -import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND; -import static com.android.server.notification.Flags.FLAG_NOTIFICATION_NLS_REBIND; import static com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT; import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE; import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled; @@ -65,14 +63,11 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.IInterface; -import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.EnableFlags; -import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; -import android.testing.TestableLooper; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -87,9 +82,7 @@ import com.android.server.UiServiceTestCase; import com.google.android.collect.Lists; -import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -110,10 +103,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; - public class ManagedServicesTest extends UiServiceTestCase { - @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); @Mock private IPackageManager mIpm; @@ -125,7 +115,6 @@ public class ManagedServicesTest extends UiServiceTestCase { private ManagedServices.UserProfiles mUserProfiles; @Mock private DevicePolicyManager mDpm; Object mLock = new Object(); - private TestableLooper mTestableLooper; UserInfo mZero = new UserInfo(0, "zero", 0); UserInfo mTen = new UserInfo(10, "ten", 0); @@ -153,7 +142,6 @@ public class ManagedServicesTest extends UiServiceTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mTestableLooper = new TestableLooper(Looper.getMainLooper()); mContext.setMockPackageManager(mPm); mContext.addMockSystemService(Context.USER_SERVICE, mUm); @@ -211,11 +199,6 @@ public class ManagedServicesTest extends UiServiceTestCase { mIpm, APPROVAL_BY_COMPONENT); } - @After - public void tearDown() throws Exception { - mTestableLooper.destroy(); - } - @Test public void testBackupAndRestore_migration() throws Exception { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { @@ -905,7 +888,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); - mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>()); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a", 0, true); service.reregisterService(cn, 0); @@ -936,7 +919,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); - mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>()); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a", 0, false); service.reregisterService(cn, 0); @@ -967,7 +950,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); - mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>()); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a/a", 0, true); service.reregisterService(cn, 0); @@ -998,7 +981,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); - mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>()); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a/a", 0, false); service.reregisterService(cn, 0); @@ -1070,78 +1053,6 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND) - public void registerService_bindingDied_rebindIsClearedOnUserSwitch() throws Exception { - Context context = mock(Context.class); - PackageManager pm = mock(PackageManager.class); - ApplicationInfo ai = new ApplicationInfo(); - ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - - when(context.getPackageName()).thenReturn(mPkg); - when(context.getUserId()).thenReturn(mUser.getIdentifier()); - when(context.getPackageManager()).thenReturn(pm); - when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); - - ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm, - APPROVAL_BY_PACKAGE); - service = spy(service); - ComponentName cn = ComponentName.unflattenFromString("a/a"); - - // Trigger onBindingDied for component when registering - // => will schedule a rebind in 10 seconds - when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> { - Object[] args = invocation.getArguments(); - ServiceConnection sc = (ServiceConnection) args[1]; - sc.onBindingDied(cn); - return true; - }); - service.registerService(cn, 0); - assertThat(service.isBound(cn, 0)).isFalse(); - - // Switch to user 10 - service.onUserSwitched(10); - - // Check that the scheduled rebind for user 0 was cleared - mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS); - mTestableLooper.processAllMessages(); - verify(service, never()).reregisterService(any(), anyInt()); - } - - @Test - public void registerService_bindingDied_rebindIsExecutedAfterTimeout() throws Exception { - Context context = mock(Context.class); - PackageManager pm = mock(PackageManager.class); - ApplicationInfo ai = new ApplicationInfo(); - ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - - when(context.getPackageName()).thenReturn(mPkg); - when(context.getUserId()).thenReturn(mUser.getIdentifier()); - when(context.getPackageManager()).thenReturn(pm); - when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); - - ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm, - APPROVAL_BY_PACKAGE); - service = spy(service); - ComponentName cn = ComponentName.unflattenFromString("a/a"); - - // Trigger onBindingDied for component when registering - // => will schedule a rebind in 10 seconds - when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> { - Object[] args = invocation.getArguments(); - ServiceConnection sc = (ServiceConnection) args[1]; - sc.onBindingDied(cn); - return true; - }); - service.registerService(cn, 0); - assertThat(service.isBound(cn, 0)).isFalse(); - - // Check that the scheduled rebind is run - mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS); - mTestableLooper.processAllMessages(); - verify(service, times(1)).reregisterService(eq(cn), eq(0)); - } - - @Test public void testPackageUninstall_packageNoLongerInApprovedList() throws Exception { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, @@ -1300,65 +1211,6 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND) - public void testUpgradeAppNoIntentFilterNoRebind() throws Exception { - Context context = spy(getContext()); - doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any()); - - ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, - mIpm, APPROVAL_BY_COMPONENT); - - List<String> packages = new ArrayList<>(); - packages.add("package"); - addExpectedServices(service, packages, 0); - - final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1"); - final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2"); - - // Both components are approved initially - mExpectedPrimaryComponentNames.clear(); - mExpectedPrimaryPackages.clear(); - mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2"); - mExpectedSecondaryComponentNames.clear(); - mExpectedSecondaryPackages.clear(); - - loadXml(service); - - //Component package/C1 loses serviceInterface intent filter - ManagedServices.Config config = service.getConfig(); - when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())) - .thenAnswer(new Answer<List<ResolveInfo>>() { - @Override - public List<ResolveInfo> answer(InvocationOnMock invocationOnMock) - throws Throwable { - Object[] args = invocationOnMock.getArguments(); - Intent invocationIntent = (Intent) args[0]; - if (invocationIntent != null) { - if (invocationIntent.getAction().equals(config.serviceInterface) - && packages.contains(invocationIntent.getPackage())) { - List<ResolveInfo> dummyServices = new ArrayList<>(); - ResolveInfo resolveInfo = new ResolveInfo(); - ServiceInfo serviceInfo = new ServiceInfo(); - serviceInfo.packageName = invocationIntent.getPackage(); - serviceInfo.name = approvedComponent.getClassName(); - serviceInfo.permission = service.getConfig().bindPermission; - resolveInfo.serviceInfo = serviceInfo; - dummyServices.add(resolveInfo); - return dummyServices; - } - } - return new ArrayList<>(); - } - }); - - // Trigger package update - service.onPackagesChanged(false, new String[]{"package"}, new int[]{0}); - - assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent)); - assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent)); - } - - @Test public void testSetPackageOrComponentEnabled() throws Exception { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, @@ -1371,21 +1223,6 @@ public class ManagedServicesTest extends UiServiceTestCase { "user10package1/K", "user10.3/Component", "user10package2/L", "user10.4/Component"})); - // mock permissions for services - PackageManager pm = mock(PackageManager.class); - when(getContext().getPackageManager()).thenReturn(pm); - List<ComponentName> enabledComponents = List.of( - ComponentName.unflattenFromString("package/Comp"), - ComponentName.unflattenFromString("package/C2"), - ComponentName.unflattenFromString("again/M4"), - ComponentName.unflattenFromString("user10package/B"), - ComponentName.unflattenFromString("user10/Component"), - ComponentName.unflattenFromString("user10package1/K"), - ComponentName.unflattenFromString("user10.3/Component"), - ComponentName.unflattenFromString("user10package2/L"), - ComponentName.unflattenFromString("user10.4/Component")); - mockServiceInfoWithMetaData(enabledComponents, service, pm, new ArrayMap<>()); - for (int userId : expectedEnabled.keySet()) { ArrayList<String> expectedForUser = expectedEnabled.get(userId); for (int i = 0; i < expectedForUser.size(); i++) { @@ -1447,90 +1284,6 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND) - public void testSetPackageOrComponentEnabled_pkgInstalledAfterEnabling() throws Exception { - ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, - mIpm, APPROVAL_BY_COMPONENT); - - final int userId = 0; - final String validComponent = "again/M4"; - ArrayList<String> expectedEnabled = Lists.newArrayList("package/Comp", "package/C2", - validComponent); - - PackageManager pm = mock(PackageManager.class); - when(getContext().getPackageManager()).thenReturn(pm); - service = spy(service); - - // Component again/M4 is a valid service and the package is available - doReturn(true).when(service) - .isValidService(ComponentName.unflattenFromString(validComponent), userId); - when(pm.isPackageAvailable("again")).thenReturn(true); - - // "package" is not available and its services are not valid - doReturn(false).when(service) - .isValidService(ComponentName.unflattenFromString("package/Comp"), userId); - doReturn(false).when(service) - .isValidService(ComponentName.unflattenFromString("package/C2"), userId); - when(pm.isPackageAvailable("package")).thenReturn(false); - - // Enable all components - for (String component: expectedEnabled) { - service.setPackageOrComponentEnabled(component, userId, true, true); - } - - // Verify everything added is approved - for (String component: expectedEnabled) { - assertTrue("Not allowed: user: " + userId + " entry: " + component - + " for approval level " + APPROVAL_BY_COMPONENT, - service.isPackageOrComponentAllowed(component, userId)); - } - - // Add missing package "package" - service.onPackagesChanged(false, new String[]{"package"}, new int[]{0}); - - // Check that component of "package" are not enabled - assertFalse(service.isComponentEnabledForCurrentProfiles( - ComponentName.unflattenFromString("package/Comp"))); - assertFalse(service.isPackageOrComponentAllowed("package/Comp", userId)); - - assertFalse(service.isComponentEnabledForCurrentProfiles( - ComponentName.unflattenFromString("package/C2"))); - assertFalse(service.isPackageOrComponentAllowed("package/C2", userId)); - - // Check that the valid components are still enabled - assertTrue(service.isComponentEnabledForCurrentProfiles( - ComponentName.unflattenFromString(validComponent))); - assertTrue(service.isPackageOrComponentAllowed(validComponent, userId)); - } - - @Test - @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND) - public void testSetPackageOrComponentEnabled_invalidComponent() throws Exception { - ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, - mIpm, APPROVAL_BY_COMPONENT); - - final int userId = 0; - final String invalidComponent = "package/Comp"; - - PackageManager pm = mock(PackageManager.class); - when(getContext().getPackageManager()).thenReturn(pm); - service = spy(service); - - // Component is an invalid service and the package is available - doReturn(false).when(service) - .isValidService(ComponentName.unflattenFromString(invalidComponent), userId); - when(pm.isPackageAvailable("package")).thenReturn(true); - service.setPackageOrComponentEnabled(invalidComponent, userId, true, true); - - // Verify that the component was not enabled - assertFalse("Not allowed: user: " + userId + " entry: " + invalidComponent - + " for approval level " + APPROVAL_BY_COMPONENT, - service.isPackageOrComponentAllowed(invalidComponent, userId)); - assertFalse(service.isComponentEnabledForCurrentProfiles( - ComponentName.unflattenFromString(invalidComponent))); - } - - @Test public void testGetAllowedPackages_byUser() throws Exception { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, @@ -2191,7 +1944,7 @@ public class ManagedServicesTest extends UiServiceTestCase { metaDataAutobindAllow.putBoolean(META_DATA_DEFAULT_AUTOBIND, true); metaDatas.put(cn_allowed, metaDataAutobindAllow); - mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas); + mockServiceInfoWithMetaData(componentNames, service, metaDatas); service.addApprovedList(cn_allowed.flattenToString(), 0, true); service.addApprovedList(cn_disallowed.flattenToString(), 0, true); @@ -2236,7 +1989,7 @@ public class ManagedServicesTest extends UiServiceTestCase { metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false); metaDatas.put(cn_disallowed, metaDataAutobindDisallow); - mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas); + mockServiceInfoWithMetaData(componentNames, service, metaDatas); service.addApprovedList(cn_disallowed.flattenToString(), 0, true); @@ -2275,7 +2028,7 @@ public class ManagedServicesTest extends UiServiceTestCase { metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false); metaDatas.put(cn_disallowed, metaDataAutobindDisallow); - mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas); + mockServiceInfoWithMetaData(componentNames, service, metaDatas); service.addApprovedList(cn_disallowed.flattenToString(), 0, true); @@ -2346,8 +2099,8 @@ public class ManagedServicesTest extends UiServiceTestCase { } private void mockServiceInfoWithMetaData(List<ComponentName> componentNames, - ManagedServices service, PackageManager packageManager, - ArrayMap<ComponentName, Bundle> metaDatas) throws RemoteException { + ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas) + throws RemoteException { when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer( (Answer<ServiceInfo>) invocation -> { ComponentName invocationCn = invocation.getArgument(0); @@ -2362,39 +2115,6 @@ public class ManagedServicesTest extends UiServiceTestCase { return null; } ); - - // add components to queryIntentServicesAsUser response - final List<String> packages = new ArrayList<>(); - for (ComponentName cn: componentNames) { - packages.add(cn.getPackageName()); - } - ManagedServices.Config config = service.getConfig(); - when(packageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())). - thenAnswer(new Answer<List<ResolveInfo>>() { - @Override - public List<ResolveInfo> answer(InvocationOnMock invocationOnMock) - throws Throwable { - Object[] args = invocationOnMock.getArguments(); - Intent invocationIntent = (Intent) args[0]; - if (invocationIntent != null) { - if (invocationIntent.getAction().equals(config.serviceInterface) - && packages.contains(invocationIntent.getPackage())) { - List<ResolveInfo> dummyServices = new ArrayList<>(); - for (ComponentName cn: componentNames) { - ResolveInfo resolveInfo = new ResolveInfo(); - ServiceInfo serviceInfo = new ServiceInfo(); - serviceInfo.packageName = invocationIntent.getPackage(); - serviceInfo.name = cn.getClassName(); - serviceInfo.permission = service.getConfig().bindPermission; - resolveInfo.serviceInfo = serviceInfo; - dummyServices.add(resolveInfo); - } - return dummyServices; - } - } - return new ArrayList<>(); - } - }); } private void resetComponentsAndPackages() { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java index 2c645e0ca353..0f7de7d78ccf 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -28,7 +28,6 @@ import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertNull; - import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.any; @@ -198,8 +197,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { public void testWriteXml_userTurnedOffNAS() throws Exception { int userId = ActivityManager.getCurrentUser(); - doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId)); - mAssistants.loadDefaultsFromConfig(true); mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true, @@ -435,10 +432,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { public void testSetPackageOrComponentEnabled_onlyOnePackage() throws Exception { ComponentName component1 = ComponentName.unflattenFromString("package/Component1"); ComponentName component2 = ComponentName.unflattenFromString("package/Component2"); - - doReturn(true).when(mAssistants).isValidService(eq(component1), eq(mZero.id)); - doReturn(true).when(mAssistants).isValidService(eq(component2), eq(mZero.id)); - mAssistants.setPackageOrComponentEnabled(component1.flattenToString(), mZero.id, true, true, true); verify(mNm, never()).setNotificationAssistantAccessGrantedForUserInternal( @@ -584,7 +577,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { public void testSetAdjustmentTypeSupportedState() throws Exception { int userId = ActivityManager.getCurrentUser(); - doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId)); mAssistants.loadDefaultsFromConfig(true); mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true, true, true); @@ -608,7 +600,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { public void testSetAdjustmentTypeSupportedState_readWriteXml_entries() throws Exception { int userId = ActivityManager.getCurrentUser(); - doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId)); mAssistants.loadDefaultsFromConfig(true); mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true, true, true); @@ -632,7 +623,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { public void testSetAdjustmentTypeSupportedState_readWriteXml_empty() throws Exception { int userId = ActivityManager.getCurrentUser(); - doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId)); mAssistants.loadDefaultsFromConfig(true); mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true, true, true); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index cbfdc5f61e3f..d33317a7168e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -43,8 +43,8 @@ import static android.app.Notification.FLAG_PROMOTED_ONGOING; import static android.app.Notification.FLAG_USER_INITIATED_JOB; import static android.app.Notification.GROUP_ALERT_CHILDREN; import static android.app.Notification.VISIBILITY_PRIVATE; -import static android.app.NotificationChannel.NEWS_ID; import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationChannel.NEWS_ID; import static android.app.NotificationChannel.PROMOTIONS_ID; import static android.app.NotificationChannel.RECS_ID; import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; @@ -78,7 +78,6 @@ import static android.app.PendingIntent.FLAG_ONE_SHOT; import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED; import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED; import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG; -import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.PackageManager.FEATURE_TELECOM; import static android.content.pm.PackageManager.FEATURE_WATCH; @@ -336,12 +335,12 @@ import com.android.server.utils.quota.MultiRateLimiter; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; -import com.google.android.collect.Lists; -import com.google.common.collect.ImmutableList; - import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; +import com.google.android.collect.Lists; +import com.google.common.collect.ImmutableList; + import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -366,7 +365,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; -import java.io.OutputStream; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; @@ -14365,9 +14363,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); } - private NotificationRecord createBigPictureRecord(boolean isBigPictureStyle, boolean hasImage, - boolean isImageBitmap, boolean isExpired) { - Notification.Builder builder = new Notification.Builder(mContext); + private Notification createBigPictureNotification(boolean isBigPictureStyle, boolean hasImage, + boolean isImageBitmap) { + Notification.Builder builder = new Notification.Builder(mContext) + .setSmallIcon(android.R.drawable.sym_def_app_icon); Notification.BigPictureStyle style = new Notification.BigPictureStyle(); if (isBigPictureStyle && hasImage) { @@ -14383,12 +14382,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification notification = builder.setChannelId(TEST_CHANNEL_ID).build(); + return notification; + } + + private NotificationRecord createBigPictureRecord(boolean isBigPictureStyle, boolean hasImage, + boolean isImageBitmap, boolean isExpired) { long timePostedMs = System.currentTimeMillis(); if (isExpired) { timePostedMs -= BITMAP_DURATION.toMillis(); } StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0, - notification, UserHandle.getUserHandleForUid(mUid), null, timePostedMs); + createBigPictureNotification(isBigPictureStyle, hasImage, isImageBitmap), + UserHandle.getUserHandleForUid(mUid), null, timePostedMs); return new NotificationRecord(mContext, sbn, mTestNotificationChannel); } @@ -14400,6 +14405,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testRemoveBitmaps_canRemoveRevokedDelegate() throws Exception { + Notification n = createBigPictureNotification(true, true, true); + long timePostedMs = System.currentTimeMillis(); + timePostedMs -= BITMAP_DURATION.toMillis(); + + when(mPermissionHelper.hasPermission(UID_O)).thenReturn(true); + when(mPackageManagerInternal.isSameApp(PKG_O, UID_O, UserHandle.getUserId(UID_O))) + .thenReturn(true); + mService.mPreferencesHelper.createNotificationChannel(PKG_O, UID_O, + mTestNotificationChannel, true /* fromTargetApp */, false, UID_O, + false); + mBinderService.createNotificationChannels(PKG_O, new ParceledListSlice( + Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel))); + + StatusBarNotification sbn = new StatusBarNotification(PKG_O, "old.delegate", 8, "tag", + UID_O, 0, n, UserHandle.getUserHandleForUid(UID_O), null, timePostedMs); + + mService.addNotification(new NotificationRecord(mContext, sbn, mTestNotificationChannel)); + mInternalService.removeBitmaps(); + + waitForIdle(); + + verify(mWorkerHandler, times(1)) + .post(any(NotificationManagerService.EnqueueNotificationRunnable.class)); + } + + @Test public void testRemoveBitmaps_notBigPicture_noRepost() { addRecordAndRemoveBitmaps( createBigPictureRecord( diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index be5c84c0353c..ac96ef28f501 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -53,6 +53,7 @@ public class TestableLooper { private static final Field MESSAGE_QUEUE_MESSAGES_FIELD; private static final Field MESSAGE_NEXT_FIELD; private static final Field MESSAGE_WHEN_FIELD; + private static Field MESSAGE_QUEUE_USE_CONCURRENT_FIELD = null; private Looper mLooper; private MessageQueue mQueue; @@ -63,6 +64,14 @@ public class TestableLooper { static { try { + MESSAGE_QUEUE_USE_CONCURRENT_FIELD = + MessageQueue.class.getDeclaredField("mUseConcurrent"); + MESSAGE_QUEUE_USE_CONCURRENT_FIELD.setAccessible(true); + } catch (NoSuchFieldException ignored) { + // Ignore - maybe this is not CombinedMessageQueue? + } + + try { MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); @@ -146,6 +155,15 @@ public class TestableLooper { mLooper = l; mQueue = mLooper.getQueue(); mHandler = new Handler(mLooper); + + // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing. + if (MESSAGE_QUEUE_USE_CONCURRENT_FIELD != null) { + try { + MESSAGE_QUEUE_USE_CONCURRENT_FIELD.set(mQueue, false); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } } /** diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java index e6eabd804294..1bcfaf60857d 100644 --- a/tests/utils/testutils/java/android/os/test/TestLooper.java +++ b/tests/utils/testutils/java/android/os/test/TestLooper.java @@ -90,20 +90,6 @@ public class TestLooper { * and call {@link #dispatchAll()}. */ public TestLooper(Clock clock) { - Field messageQueueUseConcurrentField = null; - boolean previousUseConcurrentValue = false; - try { - messageQueueUseConcurrentField = MessageQueue.class.getDeclaredField("sUseConcurrent"); - messageQueueUseConcurrentField.setAccessible(true); - previousUseConcurrentValue = messageQueueUseConcurrentField.getBoolean(null); - // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing. - messageQueueUseConcurrentField.set(null, false); - } catch (NoSuchFieldException e) { - // Ignore - maybe this is not CombinedMessageQueue? - } catch (IllegalAccessException e) { - throw new RuntimeException("Reflection error constructing or accessing looper", e); - } - try { mLooper = LOOPER_CONSTRUCTOR.newInstance(false); @@ -114,15 +100,19 @@ public class TestLooper { throw new RuntimeException("Reflection error constructing or accessing looper", e); } - mClock = clock; - - if (messageQueueUseConcurrentField != null) { - try { - messageQueueUseConcurrentField.set(null, previousUseConcurrentValue); - } catch (IllegalAccessException e) { - throw new RuntimeException("Reflection error constructing or accessing looper", e); - } + // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing. + try { + Field messageQueueUseConcurrentField = + MessageQueue.class.getDeclaredField("mUseConcurrent"); + messageQueueUseConcurrentField.setAccessible(true); + messageQueueUseConcurrentField.set(mLooper.getQueue(), false); + } catch (NoSuchFieldException e) { + // Ignore - maybe this is not CombinedMessageQueue? + } catch (IllegalAccessException e) { + throw new RuntimeException("Reflection error constructing or accessing looper", e); } + + mClock = clock; } public Looper getLooper() { |