diff options
168 files changed, 3659 insertions, 1556 deletions
diff --git a/Android.bp b/Android.bp index e03f8449891a..25238e8c0881 100644 --- a/Android.bp +++ b/Android.bp @@ -308,6 +308,8 @@ java_defaults { include_dirs: [ "frameworks/av/aidl", "frameworks/native/libs/permission/aidl", + // TODO: remove when moved to the below package + "frameworks/base/packages/ConnectivityT/framework-t/aidl-export", "packages/modules/Connectivity/framework/aidl-export", ], }, @@ -557,6 +559,9 @@ stubs_defaults { include_dirs: [ "frameworks/av/aidl", "frameworks/native/libs/permission/aidl", + // TODO: remove when moved to the below package + "frameworks/base/packages/ConnectivityT/framework-t/aidl-export", + "packages/modules/Connectivity/framework/aidl-export", ], }, // These are libs from framework-internal-utils that are required (i.e. being referenced) diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java index afad29c7eebb..c4795f55fc8a 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java @@ -364,6 +364,11 @@ public class PowerExemptionManager { * @hide */ public static final int REASON_SYSTEM_MODULE = 320; + /** + * Carrier privileged app. + * @hide + */ + public static final int REASON_CARRIER_PRIVILEGED_APP = 321; /** @hide The app requests out-out. */ public static final int REASON_OPT_OUT_REQUESTED = 1000; @@ -440,6 +445,7 @@ public class PowerExemptionManager { REASON_ROLE_DIALER, REASON_ROLE_EMERGENCY, REASON_SYSTEM_MODULE, + REASON_CARRIER_PRIVILEGED_APP, REASON_OPT_OUT_REQUESTED, }) @Retention(RetentionPolicy.SOURCE) @@ -749,6 +755,8 @@ public class PowerExemptionManager { return "ROLE_EMERGENCY"; case REASON_SYSTEM_MODULE: return "SYSTEM_MODULE"; + case REASON_CARRIER_PRIVILEGED_APP: + return "CARRIER_PRIVILEGED_APP"; case REASON_OPT_OUT_REQUESTED: return "REASON_OPT_OUT_REQUESTED"; default: 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 b2ae8ee62bc7..cea19451f005 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -357,6 +357,9 @@ public class JobSchedulerService extends com.android.server.SystemService // Putting RESTRICTED_INDEX after NEVER_INDEX to make it easier for proto dumping // (ScheduledJobStateChanged and JobStatusDumpProto). public static final int RESTRICTED_INDEX = 5; + // Putting EXEMPTED_INDEX after RESTRICTED_INDEX to make it easier for proto dumping + // (ScheduledJobStateChanged and JobStatusDumpProto). + public static final int EXEMPTED_INDEX = 6; private class ConstantsObserver implements DeviceConfig.OnPropertiesChangedListener, EconomyManagerInternal.TareStateChangeListener { @@ -2492,6 +2495,7 @@ public class JobSchedulerService extends com.android.server.SystemService shouldForceBatchJob = mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1 && job.getEffectiveStandbyBucket() != ACTIVE_INDEX + && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX && !batchDelayExpired; } @@ -2764,7 +2768,7 @@ public class JobSchedulerService extends com.android.server.SystemService return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX ? mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS : Math.min(mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS, 5 * MINUTE_IN_MILLIS); - } else if (job.getEffectivePriority() == JobInfo.PRIORITY_HIGH) { + } else if (job.getEffectivePriority() >= JobInfo.PRIORITY_HIGH) { return mConstants.RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS; } else { return mConstants.RUNTIME_MIN_GUARANTEE_MS; @@ -3086,8 +3090,10 @@ public class JobSchedulerService extends com.android.server.SystemService return FREQUENT_INDEX; } else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) { return WORKING_INDEX; - } else { + } else if (bucket > UsageStatsManager.STANDBY_BUCKET_EXEMPTED) { return ACTIVE_INDEX; + } else { + return EXEMPTED_INDEX; } } 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 0eea70106608..0456a9bfeb2e 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 @@ -17,6 +17,7 @@ package com.android.server.job.controllers; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; +import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX; import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.WORKING_INDEX; @@ -844,12 +845,15 @@ public final class JobStatus { * exemptions. */ public int getEffectiveStandbyBucket() { + final int actualBucket = getStandbyBucket(); + if (actualBucket == EXEMPTED_INDEX) { + return actualBucket; + } if (uidActive || getJob().isExemptedFromAppStandby()) { // Treat these cases as if they're in the ACTIVE bucket so that they get throttled // like other ACTIVE apps. return ACTIVE_INDEX; } - final int actualBucket = getStandbyBucket(); if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX && mHasMediaBackupExemption) { // Cap it at WORKING_INDEX as media back up jobs are important to the user, and the diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index dd5246aebbb4..c1728a3a8abe 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -21,6 +21,7 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.DateUtils.SECOND_IN_MILLIS; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; +import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX; import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RARE_INDEX; @@ -132,6 +133,7 @@ public final class QuotaController extends StateController { */ public long expirationTimeElapsed; + public long allowedTimePerPeriodMs; public long windowSizeMs; public int jobCountLimit; public int sessionCountLimit; @@ -213,6 +215,7 @@ public final class QuotaController extends StateController { @Override public String toString() { return "expirationTime=" + expirationTimeElapsed + ", " + + "allowedTimePerPeriodMs=" + allowedTimePerPeriodMs + ", " + "windowSizeMs=" + windowSizeMs + ", " + "jobCountLimit=" + jobCountLimit + ", " + "sessionCountLimit=" + sessionCountLimit + ", " @@ -236,6 +239,7 @@ public final class QuotaController extends StateController { if (obj instanceof ExecutionStats) { ExecutionStats other = (ExecutionStats) obj; return this.expirationTimeElapsed == other.expirationTimeElapsed + && this.allowedTimePerPeriodMs == other.allowedTimePerPeriodMs && this.windowSizeMs == other.windowSizeMs && this.jobCountLimit == other.jobCountLimit && this.sessionCountLimit == other.sessionCountLimit @@ -261,6 +265,7 @@ public final class QuotaController extends StateController { public int hashCode() { int result = 0; result = 31 * result + hashLong(expirationTimeElapsed); + result = 31 * result + hashLong(allowedTimePerPeriodMs); result = 31 * result + hashLong(windowSizeMs); result = 31 * result + hashLong(jobCountLimit); result = 31 * result + hashLong(sessionCountLimit); @@ -350,7 +355,15 @@ public final class QuotaController extends StateController { private boolean mIsEnabled; /** How much time each app will have to run jobs within their standby bucket window. */ - private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; + private final long[] mAllowedTimePerPeriodMs = new long[]{ + QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, + QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS, + 0, // NEVER + QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS + }; /** * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS} @@ -365,12 +378,6 @@ public final class QuotaController extends StateController { private long mQuotaBufferMs = QcConstants.DEFAULT_IN_QUOTA_BUFFER_MS; /** - * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine - * when an app will have enough quota to transition from out-of-quota to in-quota. - */ - private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; - - /** * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an * app will have enough quota to transition from out-of-quota to in-quota. */ @@ -450,7 +457,8 @@ public final class QuotaController extends StateController { QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS, QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS, 0, // NEVER - QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS + QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS, + QcConstants.DEFAULT_WINDOW_SIZE_EXEMPTED_MS }; /** The maximum period any bucket can have. */ @@ -469,7 +477,8 @@ public final class QuotaController extends StateController { QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT, QcConstants.DEFAULT_MAX_JOB_COUNT_RARE, 0, // NEVER - QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED + QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED, + QcConstants.DEFAULT_MAX_JOB_COUNT_EXEMPTED }; /** @@ -487,6 +496,7 @@ public final class QuotaController extends StateController { QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE, 0, // NEVER QcConstants.DEFAULT_MAX_SESSION_COUNT_RESTRICTED, + QcConstants.DEFAULT_MAX_SESSION_COUNT_EXEMPTED, }; /** @@ -506,7 +516,8 @@ public final class QuotaController extends StateController { QcConstants.DEFAULT_EJ_LIMIT_FREQUENT_MS, QcConstants.DEFAULT_EJ_LIMIT_RARE_MS, 0, // NEVER - QcConstants.DEFAULT_EJ_LIMIT_RESTRICTED_MS + QcConstants.DEFAULT_EJ_LIMIT_RESTRICTED_MS, + QcConstants.DEFAULT_EJ_LIMIT_EXEMPTED_MS }; private long mEjLimitAdditionInstallerMs = QcConstants.DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS; @@ -823,6 +834,11 @@ public final class QuotaController extends StateController { if (mService.isBatteryCharging()) { return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; } + if (jobStatus.getEffectiveStandbyBucket() == EXEMPTED_INDEX) { + return Math.max(mEJLimitsMs[EXEMPTED_INDEX] / 2, + getTimeUntilEJQuotaConsumedLocked( + jobStatus.getSourceUserId(), jobStatus.getSourcePackageName())); + } if (mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus)) { return Math.max(mEJLimitsMs[ACTIVE_INDEX] / 2, getTimeUntilEJQuotaConsumedLocked( @@ -929,9 +945,11 @@ public final class QuotaController extends StateController { final long minSurplus; if (priority <= JobInfo.PRIORITY_MIN) { - minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityMin); + minSurplus = (long) + (mAllowedTimePerPeriodMs[standbyBucket] * mAllowedTimeSurplusPriorityMin); } else if (priority <= JobInfo.PRIORITY_LOW) { - minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityLow); + minSurplus = (long) + (mAllowedTimePerPeriodMs[standbyBucket] * mAllowedTimeSurplusPriorityLow); } else { minSurplus = 0; } @@ -989,7 +1007,7 @@ public final class QuotaController extends StateController { } private long getRemainingExecutionTimeLocked(@NonNull ExecutionStats stats) { - return Math.min(mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs, + return Math.min(stats.allowedTimePerPeriodMs - stats.executionTimeInWindowMs, mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs); } @@ -1068,15 +1086,15 @@ public final class QuotaController extends StateController { if (sessions == null || sessions.size() == 0) { // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can // essentially run until they reach the maximum limit. - if (stats.windowSizeMs == mAllowedTimePerPeriodMs) { + if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { return mMaxExecutionTimeMs; } - return mAllowedTimePerPeriodMs; + return mAllowedTimePerPeriodMs[standbyBucket]; } final long startWindowElapsed = nowElapsed - stats.windowSizeMs; final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; - final long allowedTimePerPeriodMs = getAllowedTimePerPeriodMs(jobPriority); + final long allowedTimePerPeriodMs = getAllowedTimePerPeriodMs(standbyBucket, jobPriority); final long allowedTimeRemainingMs = allowedTimePerPeriodMs - stats.executionTimeInWindowMs; final long maxExecutionTimeRemainingMs = mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs; @@ -1087,7 +1105,7 @@ public final class QuotaController extends StateController { // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can // essentially run until they reach the maximum limit. - if (stats.windowSizeMs == mAllowedTimePerPeriodMs) { + if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { return calculateTimeUntilQuotaConsumedLocked( sessions, startMaxElapsed, maxExecutionTimeRemainingMs); } @@ -1103,14 +1121,19 @@ public final class QuotaController extends StateController { sessions, startWindowElapsed, allowedTimeRemainingMs)); } - private long getAllowedTimePerPeriodMs(@JobInfo.Priority int jobPriority) { + private long getAllowedTimePerPeriodMs(int standbyBucket, @JobInfo.Priority int jobPriority) { + return getAllowedTimePerPeriodMs(mAllowedTimePerPeriodMs[standbyBucket], jobPriority); + } + + private long getAllowedTimePerPeriodMs(long initialAllowedTime, + @JobInfo.Priority int jobPriority) { if (jobPriority <= JobInfo.PRIORITY_MIN) { - return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityMin)); + return (long) (initialAllowedTime * (1 - mAllowedTimeSurplusPriorityMin)); } if (jobPriority <= JobInfo.PRIORITY_LOW) { - return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityLow)); + return (long) (initialAllowedTime * (1 - mAllowedTimeSurplusPriorityLow)); } - return mAllowedTimePerPeriodMs; + return initialAllowedTime; } /** @@ -1237,16 +1260,19 @@ public final class QuotaController extends StateController { appStats[standbyBucket] = stats; } if (refreshStatsIfOld) { + final long bucketAllowedTimeMs = mAllowedTimePerPeriodMs[standbyBucket]; final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket]; final int jobCountLimit = mMaxBucketJobCounts[standbyBucket]; final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket]; Timer timer = mPkgTimers.get(userId, packageName); if ((timer != null && timer.isActive()) || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis() + || stats.allowedTimePerPeriodMs != bucketAllowedTimeMs || stats.windowSizeMs != bucketWindowSizeMs || stats.jobCountLimit != jobCountLimit || stats.sessionCountLimit != sessionCountLimit) { // The stats are no longer valid. + stats.allowedTimePerPeriodMs = bucketAllowedTimeMs; stats.windowSizeMs = bucketWindowSizeMs; stats.jobCountLimit = jobCountLimit; stats.sessionCountLimit = sessionCountLimit; @@ -1272,8 +1298,11 @@ public final class QuotaController extends StateController { } else { stats.inQuotaTimeElapsed = 0; } - final long allowedTimeMinMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_MIN); - final long allowedTimeLowMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_LOW); + final long allowedTimeMinMs = + getAllowedTimePerPeriodMs(stats.allowedTimePerPeriodMs, JobInfo.PRIORITY_MIN); + final long allowedTimeLowMs = + getAllowedTimePerPeriodMs(stats.allowedTimePerPeriodMs, JobInfo.PRIORITY_LOW); + final long allowedTimeIntoQuotaMs = stats.allowedTimePerPeriodMs - mQuotaBufferMs; Timer timer = mPkgTimers.get(userId, packageName); final long nowElapsed = sElapsedRealtimeClock.millis(); @@ -1287,9 +1316,9 @@ public final class QuotaController extends StateController { // If the timer is active, the value will be stale at the next method call, so // invalidate now. stats.expirationTimeElapsed = nowElapsed; - if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) { + if (stats.executionTimeInWindowMs >= allowedTimeIntoQuotaMs) { stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, - nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs); + nowElapsed - allowedTimeIntoQuotaMs + stats.windowSizeMs); } if (stats.executionTimeInWindowMs >= allowedTimeLowMs) { stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, @@ -1346,9 +1375,9 @@ public final class QuotaController extends StateController { stats.executionTimeInWindowMs += session.endTimeElapsed - start; stats.bgJobCountInWindow += session.bgJobCount; - if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) { + if (stats.executionTimeInWindowMs >= allowedTimeIntoQuotaMs) { stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, - start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs + start + stats.executionTimeInWindowMs - allowedTimeIntoQuotaMs + stats.windowSizeMs); } if (stats.executionTimeInWindowMs >= allowedTimeLowMs) { @@ -1697,7 +1726,7 @@ public final class QuotaController extends StateController { if (js.setQuotaConstraintSatisfied(nowElapsed, true)) { changedJobs.add(js); } - } else if (realStandbyBucket != ACTIVE_INDEX + } else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX && realStandbyBucket == js.getEffectiveStandbyBucket() && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) { // An app in the ACTIVE bucket may be out of quota while the job could be in quota @@ -1842,7 +1871,8 @@ public final class QuotaController extends StateController { } } final boolean inRegularQuota = - stats.executionTimeInWindowMs < getAllowedTimePerPeriodMs(minPriority) + stats.executionTimeInWindowMs + < getAllowedTimePerPeriodMs(standbyBucket, minPriority) && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs && isUnderJobCountQuota && isUnderTimingSessionCountQuota; @@ -2921,9 +2951,29 @@ public final class QuotaController extends StateController { /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */ private static final String QC_CONSTANT_PREFIX = "qc_"; + /** + * Previously used keys: + * * allowed_time_per_period_ms -- No longer used after splitting by bucket + */ + + @VisibleForTesting + static final String KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + QC_CONSTANT_PREFIX + "allowed_time_per_period_exempted_ms"; + @VisibleForTesting + static final String KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + QC_CONSTANT_PREFIX + "allowed_time_per_period_active_ms"; + @VisibleForTesting + static final String KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS = + QC_CONSTANT_PREFIX + "allowed_time_per_period_working_ms"; @VisibleForTesting - static final String KEY_ALLOWED_TIME_PER_PERIOD_MS = - QC_CONSTANT_PREFIX + "allowed_time_per_period_ms"; + static final String KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = + QC_CONSTANT_PREFIX + "allowed_time_per_period_frequent_ms"; + @VisibleForTesting + static final String KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS = + QC_CONSTANT_PREFIX + "allowed_time_per_period_rare_ms"; + @VisibleForTesting + static final String KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = + QC_CONSTANT_PREFIX + "allowed_time_per_period_restricted_ms"; @VisibleForTesting static final String KEY_IN_QUOTA_BUFFER_MS = QC_CONSTANT_PREFIX + "in_quota_buffer_ms"; @@ -2934,6 +2984,9 @@ public final class QuotaController extends StateController { static final String KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN = QC_CONSTANT_PREFIX + "allowed_time_surplus_priority_min"; @VisibleForTesting + static final String KEY_WINDOW_SIZE_EXEMPTED_MS = + QC_CONSTANT_PREFIX + "window_size_exempted_ms"; + @VisibleForTesting static final String KEY_WINDOW_SIZE_ACTIVE_MS = QC_CONSTANT_PREFIX + "window_size_active_ms"; @VisibleForTesting @@ -2952,6 +3005,9 @@ public final class QuotaController extends StateController { static final String KEY_MAX_EXECUTION_TIME_MS = QC_CONSTANT_PREFIX + "max_execution_time_ms"; @VisibleForTesting + static final String KEY_MAX_JOB_COUNT_EXEMPTED = + QC_CONSTANT_PREFIX + "max_job_count_exempted"; + @VisibleForTesting static final String KEY_MAX_JOB_COUNT_ACTIVE = QC_CONSTANT_PREFIX + "max_job_count_active"; @VisibleForTesting @@ -2973,6 +3029,9 @@ public final class QuotaController extends StateController { static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = QC_CONSTANT_PREFIX + "max_job_count_per_rate_limiting_window"; @VisibleForTesting + static final String KEY_MAX_SESSION_COUNT_EXEMPTED = + QC_CONSTANT_PREFIX + "max_session_count_exempted"; + @VisibleForTesting static final String KEY_MAX_SESSION_COUNT_ACTIVE = QC_CONSTANT_PREFIX + "max_session_count_active"; @VisibleForTesting @@ -2997,6 +3056,9 @@ public final class QuotaController extends StateController { static final String KEY_MIN_QUOTA_CHECK_DELAY_MS = QC_CONSTANT_PREFIX + "min_quota_check_delay_ms"; @VisibleForTesting + static final String KEY_EJ_LIMIT_EXEMPTED_MS = + QC_CONSTANT_PREFIX + "ej_limit_exempted_ms"; + @VisibleForTesting static final String KEY_EJ_LIMIT_ACTIVE_MS = QC_CONSTANT_PREFIX + "ej_limit_active_ms"; @VisibleForTesting @@ -3039,14 +3101,26 @@ public final class QuotaController extends StateController { static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS = QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms"; - private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_MS = + private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + 10 * 60 * 1000L; // 10 minutes + private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + 10 * 60 * 1000L; // 10 minutes + private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS = + 10 * 60 * 1000L; // 10 minutes + private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = + 10 * 60 * 1000L; // 10 minutes + private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS = + 10 * 60 * 1000L; // 10 minutes + private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = 10 * 60 * 1000L; // 10 minutes private static final long DEFAULT_IN_QUOTA_BUFFER_MS = 30 * 1000L; // 30 seconds private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW = .25f; private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN = .5f; + private static final long DEFAULT_WINDOW_SIZE_EXEMPTED_MS = + DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // EXEMPT apps can run jobs at any time private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS = - DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time + DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // ACTIVE apps can run jobs at any time private static final long DEFAULT_WINDOW_SIZE_WORKING_MS = 2 * 60 * 60 * 1000L; // 2 hours private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS = @@ -3060,8 +3134,9 @@ public final class QuotaController extends StateController { private static final long DEFAULT_RATE_LIMITING_WINDOW_MS = MINUTE_IN_MILLIS; private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20; - private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = + private static final int DEFAULT_MAX_JOB_COUNT_EXEMPTED = 75; // 75/window = 450/hr = 1/session + private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_EXEMPTED; private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS); private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session @@ -3069,8 +3144,10 @@ public final class QuotaController extends StateController { private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS); private static final int DEFAULT_MAX_JOB_COUNT_RESTRICTED = 10; - private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE = + private static final int DEFAULT_MAX_SESSION_COUNT_EXEMPTED = 75; // 450/hr + private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE = + DEFAULT_MAX_SESSION_COUNT_EXEMPTED; private static final int DEFAULT_MAX_SESSION_COUNT_WORKING = 10; // 5/hr private static final int DEFAULT_MAX_SESSION_COUNT_FREQUENT = @@ -3081,6 +3158,7 @@ public final class QuotaController extends StateController { private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20; private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds private static final long DEFAULT_MIN_QUOTA_CHECK_DELAY_MS = MINUTE_IN_MILLIS; + private static final long DEFAULT_EJ_LIMIT_EXEMPTED_MS = 45 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_LIMIT_ACTIVE_MS = 30 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS; private static final long DEFAULT_EJ_LIMIT_FREQUENT_MS = 10 * MINUTE_IN_MILLIS; @@ -3096,8 +3174,39 @@ public final class QuotaController extends StateController { private static final long DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS = 1 * MINUTE_IN_MILLIS; - /** How much time each app will have to run jobs within their standby bucket window. */ - public long ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; + /** + * How much time each app in the exempted bucket will have to run jobs within their standby + * bucket window. + */ + public long ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; + /** + * How much time each app in the active bucket will have to run jobs within their standby + * bucket window. + */ + public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + /** + * How much time each app in the working set bucket will have to run jobs within their + * standby bucket window. + */ + public long ALLOWED_TIME_PER_PERIOD_WORKING_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS; + /** + * How much time each app in the frequent bucket will have to run jobs within their standby + * bucket window. + */ + public long ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = + DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS; + /** + * How much time each app in the rare bucket will have to run jobs within their standby + * bucket window. + */ + public long ALLOWED_TIME_PER_PERIOD_RARE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS; + /** + * How much time each app in the restricted bucket will have to run jobs within their + * standby bucket window. + */ + public long ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = + DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS; /** * How much time the package should have before transitioning from out-of-quota to in-quota. @@ -3106,7 +3215,7 @@ public final class QuotaController extends StateController { public long IN_QUOTA_BUFFER_MS = DEFAULT_IN_QUOTA_BUFFER_MS; /** - * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by + * The percentage of ALLOWED_TIME_PER_PERIOD_*_MS that should not be used by * {@link JobInfo#PRIORITY_LOW low priority} jobs. In other words, there must be a minimum * surplus of this amount of remaining allowed time before we start running low priority * jobs. @@ -3114,7 +3223,7 @@ public final class QuotaController extends StateController { public float ALLOWED_TIME_SURPLUS_PRIORITY_LOW = DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW; /** - * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by + * The percentage of ALLOWED_TIME_PER_PERIOD_*_MS that should not be used by * {@link JobInfo#PRIORITY_MIN low priority} jobs. In other words, there must be a minimum * surplus of this amount of remaining allowed time before we start running min priority * jobs. @@ -3123,35 +3232,42 @@ public final class QuotaController extends StateController { /** * The quota window size of the particular standby bucket. Apps in this standby bucket are - * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past + * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS} within the past + * WINDOW_SIZE_MS. + */ + public long WINDOW_SIZE_EXEMPTED_MS = DEFAULT_WINDOW_SIZE_EXEMPTED_MS; + + /** + * The quota window size of the particular standby bucket. Apps in this standby bucket are + * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_ACTIVE_MS} within the past * WINDOW_SIZE_MS. */ public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_WINDOW_SIZE_ACTIVE_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are - * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past + * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_WORKING_MS} within the past * WINDOW_SIZE_MS. */ public long WINDOW_SIZE_WORKING_MS = DEFAULT_WINDOW_SIZE_WORKING_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are - * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past + * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_FREQUENT_MS} within the past * WINDOW_SIZE_MS. */ public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_WINDOW_SIZE_FREQUENT_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are - * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past + * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_RARE_MS} within the past * WINDOW_SIZE_MS. */ public long WINDOW_SIZE_RARE_MS = DEFAULT_WINDOW_SIZE_RARE_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are - * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past + * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS} within the past * WINDOW_SIZE_MS. */ public long WINDOW_SIZE_RESTRICTED_MS = DEFAULT_WINDOW_SIZE_RESTRICTED_MS; @@ -3165,6 +3281,12 @@ public final class QuotaController extends StateController { * The maximum number of jobs an app can run within this particular standby bucket's * window size. */ + public int MAX_JOB_COUNT_EXEMPTED = DEFAULT_MAX_JOB_COUNT_EXEMPTED; + + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ public int MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_ACTIVE; /** @@ -3204,6 +3326,12 @@ public final class QuotaController extends StateController { * The maximum number of {@link TimingSession TimingSessions} an app can run within this * particular standby bucket's window size. */ + public int MAX_SESSION_COUNT_EXEMPTED = DEFAULT_MAX_SESSION_COUNT_EXEMPTED; + + /** + * The maximum number of {@link TimingSession TimingSessions} an app can run within this + * particular standby bucket's window size. + */ public int MAX_SESSION_COUNT_ACTIVE = DEFAULT_MAX_SESSION_COUNT_ACTIVE; /** @@ -3232,7 +3360,7 @@ public final class QuotaController extends StateController { /** * The maximum number of {@link TimingSession TimingSessions} that can run within the past - * {@link #ALLOWED_TIME_PER_PERIOD_MS}. + * {@link #RATE_LIMITING_WINDOW_MS}. */ public int MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; @@ -3275,6 +3403,13 @@ public final class QuotaController extends StateController { * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring * in any rewards or free EJs). */ + public long EJ_LIMIT_EXEMPTED_MS = DEFAULT_EJ_LIMIT_EXEMPTED_MS; + + /** + * The total expedited job session limit of the particular standby bucket. Apps in this + * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring + * in any rewards or free EJs). + */ public long EJ_LIMIT_ACTIVE_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS; /** @@ -3358,7 +3493,12 @@ public final class QuotaController extends StateController { public void processConstantLocked(@NonNull DeviceConfig.Properties properties, @NonNull String key) { switch (key) { - case KEY_ALLOWED_TIME_PER_PERIOD_MS: + case KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS: + case KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS: + case KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS: + case KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS: + case KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS: + case KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS: case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW: case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN: case KEY_IN_QUOTA_BUFFER_MS: @@ -3388,6 +3528,15 @@ public final class QuotaController extends StateController { updateEJLimitConstantsLocked(); break; + case KEY_MAX_JOB_COUNT_EXEMPTED: + MAX_JOB_COUNT_EXEMPTED = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_EXEMPTED); + int newExemptedMaxJobCount = + Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_EXEMPTED); + if (mMaxBucketJobCounts[EXEMPTED_INDEX] != newExemptedMaxJobCount) { + mMaxBucketJobCounts[EXEMPTED_INDEX] = newExemptedMaxJobCount; + mShouldReevaluateConstraints = true; + } + break; case KEY_MAX_JOB_COUNT_ACTIVE: MAX_JOB_COUNT_ACTIVE = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_ACTIVE); int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE); @@ -3432,6 +3581,16 @@ public final class QuotaController extends StateController { mShouldReevaluateConstraints = true; } break; + case KEY_MAX_SESSION_COUNT_EXEMPTED: + MAX_SESSION_COUNT_EXEMPTED = + properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_EXEMPTED); + int newExemptedMaxSessionCount = + Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_EXEMPTED); + if (mMaxBucketSessionCounts[EXEMPTED_INDEX] != newExemptedMaxSessionCount) { + mMaxBucketSessionCounts[EXEMPTED_INDEX] = newExemptedMaxSessionCount; + mShouldReevaluateConstraints = true; + } + break; case KEY_MAX_SESSION_COUNT_ACTIVE: MAX_SESSION_COUNT_ACTIVE = properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_ACTIVE); @@ -3579,15 +3738,34 @@ public final class QuotaController extends StateController { // Query the values as an atomic set. final DeviceConfig.Properties properties = DeviceConfig.getProperties( DeviceConfig.NAMESPACE_JOB_SCHEDULER, - KEY_ALLOWED_TIME_PER_PERIOD_MS, KEY_IN_QUOTA_BUFFER_MS, + KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, + KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + KEY_IN_QUOTA_BUFFER_MS, KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, - KEY_MAX_EXECUTION_TIME_MS, KEY_WINDOW_SIZE_ACTIVE_MS, + KEY_MAX_EXECUTION_TIME_MS, + KEY_WINDOW_SIZE_EXEMPTED_MS, KEY_WINDOW_SIZE_ACTIVE_MS, KEY_WINDOW_SIZE_WORKING_MS, KEY_WINDOW_SIZE_FREQUENT_MS, KEY_WINDOW_SIZE_RARE_MS, KEY_WINDOW_SIZE_RESTRICTED_MS); - ALLOWED_TIME_PER_PERIOD_MS = - properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_MS, - DEFAULT_ALLOWED_TIME_PER_PERIOD_MS); + ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, + DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS); + ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS); + ALLOWED_TIME_PER_PERIOD_WORKING_MS = + properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS); + ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = + properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, + DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS); + ALLOWED_TIME_PER_PERIOD_RARE_MS = + properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, + DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS); + ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = + properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS); ALLOWED_TIME_SURPLUS_PRIORITY_LOW = properties.getFloat(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW); @@ -3598,6 +3776,8 @@ public final class QuotaController extends StateController { DEFAULT_IN_QUOTA_BUFFER_MS); MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS); + WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS, + DEFAULT_WINDOW_SIZE_EXEMPTED_MS); WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS, DEFAULT_WINDOW_SIZE_ACTIVE_MS); WINDOW_SIZE_WORKING_MS = @@ -3618,20 +3798,55 @@ public final class QuotaController extends StateController { mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; mShouldReevaluateConstraints = true; } - long newAllowedTimeMs = Math.min(mMaxExecutionTimeMs, - Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_MS)); - if (mAllowedTimePerPeriodMs != newAllowedTimeMs) { - mAllowedTimePerPeriodMs = newAllowedTimeMs; - mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; + long minAllowedTimeMs = Long.MAX_VALUE; + long newAllowedTimeExemptedMs = Math.min(mMaxExecutionTimeMs, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS)); + minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeExemptedMs); + if (mAllowedTimePerPeriodMs[EXEMPTED_INDEX] != newAllowedTimeExemptedMs) { + mAllowedTimePerPeriodMs[EXEMPTED_INDEX] = newAllowedTimeExemptedMs; + mShouldReevaluateConstraints = true; + } + long newAllowedTimeActiveMs = Math.min(mMaxExecutionTimeMs, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS)); + minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeActiveMs); + if (mAllowedTimePerPeriodMs[ACTIVE_INDEX] != newAllowedTimeActiveMs) { + mAllowedTimePerPeriodMs[ACTIVE_INDEX] = newAllowedTimeActiveMs; + mShouldReevaluateConstraints = true; + } + long newAllowedTimeWorkingMs = Math.min(mMaxExecutionTimeMs, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_WORKING_MS)); + minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeWorkingMs); + if (mAllowedTimePerPeriodMs[WORKING_INDEX] != newAllowedTimeWorkingMs) { + mAllowedTimePerPeriodMs[WORKING_INDEX] = newAllowedTimeWorkingMs; + mShouldReevaluateConstraints = true; + } + long newAllowedTimeFrequentMs = Math.min(mMaxExecutionTimeMs, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_FREQUENT_MS)); + minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeFrequentMs); + if (mAllowedTimePerPeriodMs[FREQUENT_INDEX] != newAllowedTimeFrequentMs) { + mAllowedTimePerPeriodMs[FREQUENT_INDEX] = newAllowedTimeFrequentMs; + mShouldReevaluateConstraints = true; + } + long newAllowedTimeRareMs = Math.min(mMaxExecutionTimeMs, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_RARE_MS)); + minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeRareMs); + if (mAllowedTimePerPeriodMs[RARE_INDEX] != newAllowedTimeRareMs) { + mAllowedTimePerPeriodMs[RARE_INDEX] = newAllowedTimeRareMs; + mShouldReevaluateConstraints = true; + } + long newAllowedTimeRestrictedMs = Math.min(mMaxExecutionTimeMs, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS)); + minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeRestrictedMs); + if (mAllowedTimePerPeriodMs[RESTRICTED_INDEX] != newAllowedTimeRestrictedMs) { + mAllowedTimePerPeriodMs[RESTRICTED_INDEX] = newAllowedTimeRestrictedMs; mShouldReevaluateConstraints = true; } // Make sure quota buffer is non-negative, not greater than allowed time per period, // and no more than 5 minutes. - long newQuotaBufferMs = Math.max(0, Math.min(mAllowedTimePerPeriodMs, + long newQuotaBufferMs = Math.max(0, Math.min(minAllowedTimeMs, Math.min(5 * MINUTE_IN_MILLIS, IN_QUOTA_BUFFER_MS))); if (mQuotaBufferMs != newQuotaBufferMs) { mQuotaBufferMs = newQuotaBufferMs; - mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; mShouldReevaluateConstraints = true; } @@ -3652,32 +3867,38 @@ public final class QuotaController extends StateController { mAllowedTimeSurplusPriorityMin = newAllowedTimeSurplusPriorityMin; mShouldReevaluateConstraints = true; } - long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs, + long newExemptedPeriodMs = Math.max(mAllowedTimePerPeriodMs[EXEMPTED_INDEX], + Math.min(MAX_PERIOD_MS, WINDOW_SIZE_EXEMPTED_MS)); + if (mBucketPeriodsMs[EXEMPTED_INDEX] != newExemptedPeriodMs) { + mBucketPeriodsMs[EXEMPTED_INDEX] = newExemptedPeriodMs; + mShouldReevaluateConstraints = true; + } + long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs[ACTIVE_INDEX], Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS)); if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) { mBucketPeriodsMs[ACTIVE_INDEX] = newActivePeriodMs; mShouldReevaluateConstraints = true; } - long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs, + long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs[WORKING_INDEX], Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS)); if (mBucketPeriodsMs[WORKING_INDEX] != newWorkingPeriodMs) { mBucketPeriodsMs[WORKING_INDEX] = newWorkingPeriodMs; mShouldReevaluateConstraints = true; } - long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs, + long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs[FREQUENT_INDEX], Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS)); if (mBucketPeriodsMs[FREQUENT_INDEX] != newFrequentPeriodMs) { mBucketPeriodsMs[FREQUENT_INDEX] = newFrequentPeriodMs; mShouldReevaluateConstraints = true; } - long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs, + long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs[RARE_INDEX], Math.min(MAX_PERIOD_MS, WINDOW_SIZE_RARE_MS)); if (mBucketPeriodsMs[RARE_INDEX] != newRarePeriodMs) { mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs; mShouldReevaluateConstraints = true; } // Fit in the range [allowed time (10 mins), 1 week]. - long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs, + long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs[RESTRICTED_INDEX], Math.min(7 * 24 * 60 * MINUTE_IN_MILLIS, WINDOW_SIZE_RESTRICTED_MS)); if (mBucketPeriodsMs[RESTRICTED_INDEX] != newRestrictedPeriodMs) { mBucketPeriodsMs[RESTRICTED_INDEX] = newRestrictedPeriodMs; @@ -3740,11 +3961,14 @@ public final class QuotaController extends StateController { // Query the values as an atomic set. final DeviceConfig.Properties properties = DeviceConfig.getProperties( DeviceConfig.NAMESPACE_JOB_SCHEDULER, + KEY_EJ_LIMIT_EXEMPTED_MS, KEY_EJ_LIMIT_ACTIVE_MS, KEY_EJ_LIMIT_WORKING_MS, KEY_EJ_LIMIT_FREQUENT_MS, KEY_EJ_LIMIT_RARE_MS, KEY_EJ_LIMIT_RESTRICTED_MS, KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, KEY_EJ_WINDOW_SIZE_MS); + EJ_LIMIT_EXEMPTED_MS = properties.getLong( + KEY_EJ_LIMIT_EXEMPTED_MS, DEFAULT_EJ_LIMIT_EXEMPTED_MS); EJ_LIMIT_ACTIVE_MS = properties.getLong( KEY_EJ_LIMIT_ACTIVE_MS, DEFAULT_EJ_LIMIT_ACTIVE_MS); EJ_LIMIT_WORKING_MS = properties.getLong( @@ -3770,8 +3994,15 @@ public final class QuotaController extends StateController { mShouldReevaluateConstraints = true; } // The limit must be in the range [15 minutes, window size]. + long newExemptLimitMs = Math.max(15 * MINUTE_IN_MILLIS, + Math.min(newWindowSizeMs, EJ_LIMIT_EXEMPTED_MS)); + if (mEJLimitsMs[EXEMPTED_INDEX] != newExemptLimitMs) { + mEJLimitsMs[EXEMPTED_INDEX] = newExemptLimitMs; + mShouldReevaluateConstraints = true; + } + // The limit must be in the range [15 minutes, exempted limit]. long newActiveLimitMs = Math.max(15 * MINUTE_IN_MILLIS, - Math.min(newWindowSizeMs, EJ_LIMIT_ACTIVE_MS)); + Math.min(newExemptLimitMs, EJ_LIMIT_ACTIVE_MS)); if (mEJLimitsMs[ACTIVE_INDEX] != newActiveLimitMs) { mEJLimitsMs[ACTIVE_INDEX] = newActiveLimitMs; mShouldReevaluateConstraints = true; @@ -3823,18 +4054,31 @@ public final class QuotaController extends StateController { pw.println(); pw.println("QuotaController:"); pw.increaseIndent(); - pw.print(KEY_ALLOWED_TIME_PER_PERIOD_MS, ALLOWED_TIME_PER_PERIOD_MS).println(); + pw.print(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS) + .println(); + pw.print(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS) + .println(); + pw.print(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, ALLOWED_TIME_PER_PERIOD_WORKING_MS) + .println(); + pw.print(KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, ALLOWED_TIME_PER_PERIOD_FREQUENT_MS) + .println(); + pw.print(KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, ALLOWED_TIME_PER_PERIOD_RARE_MS) + .println(); + pw.print(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS).println(); pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, ALLOWED_TIME_SURPLUS_PRIORITY_LOW) .println(); pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, ALLOWED_TIME_SURPLUS_PRIORITY_MIN) .println(); pw.print(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println(); + pw.print(KEY_WINDOW_SIZE_EXEMPTED_MS, WINDOW_SIZE_EXEMPTED_MS).println(); pw.print(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println(); pw.print(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println(); pw.print(KEY_WINDOW_SIZE_FREQUENT_MS, WINDOW_SIZE_FREQUENT_MS).println(); pw.print(KEY_WINDOW_SIZE_RARE_MS, WINDOW_SIZE_RARE_MS).println(); pw.print(KEY_WINDOW_SIZE_RESTRICTED_MS, WINDOW_SIZE_RESTRICTED_MS).println(); pw.print(KEY_MAX_EXECUTION_TIME_MS, MAX_EXECUTION_TIME_MS).println(); + pw.print(KEY_MAX_JOB_COUNT_EXEMPTED, MAX_JOB_COUNT_EXEMPTED).println(); pw.print(KEY_MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE).println(); pw.print(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println(); pw.print(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println(); @@ -3843,6 +4087,7 @@ public final class QuotaController extends StateController { pw.print(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println(); pw.print(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println(); + pw.print(KEY_MAX_SESSION_COUNT_EXEMPTED, MAX_SESSION_COUNT_EXEMPTED).println(); pw.print(KEY_MAX_SESSION_COUNT_ACTIVE, MAX_SESSION_COUNT_ACTIVE).println(); pw.print(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println(); pw.print(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println(); @@ -3854,6 +4099,7 @@ public final class QuotaController extends StateController { TIMING_SESSION_COALESCING_DURATION_MS).println(); pw.print(KEY_MIN_QUOTA_CHECK_DELAY_MS, MIN_QUOTA_CHECK_DELAY_MS).println(); + pw.print(KEY_EJ_LIMIT_EXEMPTED_MS, EJ_LIMIT_EXEMPTED_MS).println(); pw.print(KEY_EJ_LIMIT_ACTIVE_MS, EJ_LIMIT_ACTIVE_MS).println(); pw.print(KEY_EJ_LIMIT_WORKING_MS, EJ_LIMIT_WORKING_MS).println(); pw.print(KEY_EJ_LIMIT_FREQUENT_MS, EJ_LIMIT_FREQUENT_MS).println(); @@ -3875,8 +4121,6 @@ public final class QuotaController extends StateController { private void dump(ProtoOutputStream proto) { final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER); - proto.write(ConstantsProto.QuotaController.ALLOWED_TIME_PER_PERIOD_MS, - ALLOWED_TIME_PER_PERIOD_MS); proto.write(ConstantsProto.QuotaController.IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS); proto.write(ConstantsProto.QuotaController.ACTIVE_WINDOW_SIZE_MS, WINDOW_SIZE_ACTIVE_MS); @@ -3946,7 +4190,7 @@ public final class QuotaController extends StateController { //////////////////////// TESTING HELPERS ///////////////////////////// @VisibleForTesting - long getAllowedTimePerPeriodMs() { + long[] getAllowedTimePerPeriodMs() { return mAllowedTimePerPeriodMs; } diff --git a/core/api/current.txt b/core/api/current.txt index 47bcb27f5361..4777e3c83d11 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -31524,6 +31524,7 @@ package android.os { method public void setDataCapacity(int); method public void setDataPosition(int); method public void setDataSize(int); + method public void setPropagateAllowBlocking(); method public void unmarshall(@NonNull byte[], int, int); method public void writeArray(@Nullable Object[]); method public void writeBinderArray(@Nullable android.os.IBinder[]); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 6e7bc765c157..4448a032dc00 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -192,7 +192,7 @@ package android.media { method public void adjustSuggestedStreamVolumeForUid(int, int, int, @NonNull String, int, int, int); method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getHwOffloadFormatsSupportedForA2dp(); method @NonNull public java.util.List<android.bluetooth.BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeAudio(); - method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void handleBluetoothActiveDeviceChanged(@Nullable android.bluetooth.BluetoothDevice, @Nullable android.bluetooth.BluetoothDevice, @NonNull android.media.BtProfileConnectionInfo); + method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void handleBluetoothActiveDeviceChanged(@Nullable android.bluetooth.BluetoothDevice, @Nullable android.bluetooth.BluetoothDevice, @NonNull android.media.BluetoothProfileConnectionInfo); method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setA2dpSuspended(boolean); method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setBluetoothHeadsetProperties(@NonNull String, boolean, boolean); method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setHfpEnabled(boolean); @@ -202,18 +202,18 @@ package android.media { field public static final int FLAG_FROM_KEY = 4096; // 0x1000 } - public final class BtProfileConnectionInfo implements android.os.Parcelable { - method @NonNull public static android.media.BtProfileConnectionInfo a2dpInfo(boolean, int); - method @NonNull public static android.media.BtProfileConnectionInfo a2dpSinkInfo(int); + public final class BluetoothProfileConnectionInfo implements android.os.Parcelable { + method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpInfo(boolean, int); + method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpSinkInfo(int); + method @NonNull public static android.media.BluetoothProfileConnectionInfo createHearingAidInfo(boolean); + method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioInfo(boolean, boolean); method public int describeContents(); - method public boolean getIsLeOutput(); method public int getProfile(); - method public boolean getSuppressNoisyIntent(); method public int getVolume(); - method @NonNull public static android.media.BtProfileConnectionInfo hearingAidInfo(boolean); - method @NonNull public static android.media.BtProfileConnectionInfo leAudio(boolean, boolean); + method public boolean isLeOutput(); + method public boolean isSuppressNoisyIntent(); method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.media.BtProfileConnectionInfo> CREATOR; + field @NonNull public static final android.os.Parcelable.Creator<android.media.BluetoothProfileConnectionInfo> CREATOR; } public class MediaMetadataRetriever implements java.lang.AutoCloseable { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index fd716f341540..1cba58c391dc 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -910,6 +910,19 @@ package android.app { method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public int getNavBarModeOverride(); method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean); method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setNavBarModeOverride(int); + method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay(int, @NonNull android.media.MediaRoute2Info); + method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferSenderDisplay(int, @NonNull android.media.MediaRoute2Info, @Nullable java.util.concurrent.Executor, @Nullable Runnable); + field public static final int MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER = 0; // 0x0 + field public static final int MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER = 1; // 0x1 + field public static final int MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST = 1; // 0x1 + field public static final int MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST = 0; // 0x0 + field public static final int MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER = 8; // 0x8 + field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED = 6; // 0x6 + field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED = 4; // 0x4 + field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED = 2; // 0x2 + field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED = 7; // 0x7 + field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED = 5; // 0x5 + field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED = 3; // 0x3 field public static final int NAV_BAR_MODE_OVERRIDE_KIDS = 1; // 0x1 field public static final int NAV_BAR_MODE_OVERRIDE_NONE = 0; // 0x0 } @@ -9122,6 +9135,7 @@ package android.os { } public static class Build.VERSION { + field @NonNull public static final java.util.Set<java.lang.String> KNOWN_CODENAMES; field @NonNull public static final String PREVIEW_SDK_FINGERPRINT; } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index e2a78c6414d2..fea739668512 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1741,8 +1741,11 @@ package android.os { public final class Parcel { method public boolean allowSquashing(); + method public int getFlags(); method public int readExceptionCode(); method public void restoreAllowSquashing(boolean); + field public static final int FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT = 1; // 0x1 + field public static final int FLAG_PROPAGATE_ALLOW_BLOCKING = 2; // 0x2 } public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable { @@ -2741,6 +2744,7 @@ package android.view { method @NonNull public android.view.Display.Mode getDefaultMode(); method @NonNull public int[] getReportedHdrTypes(); method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut(); + method @Nullable public android.view.Display.Mode getSystemPreferredDisplayMode(); method public int getType(); method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode(); method public boolean hasAccess(int); diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 56c301f30d5f..8fcb07f578e8 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -16,6 +16,7 @@ package android.app; +import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,6 +28,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.graphics.drawable.Icon; +import android.media.MediaRoute2Info; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -39,6 +41,7 @@ import android.view.View; import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.statusbar.IUndoMediaTransferCallback; import com.android.internal.statusbar.NotificationVisibility; import java.lang.annotation.Retention; @@ -338,6 +341,166 @@ public class StatusBarManager { @Retention(RetentionPolicy.SOURCE) public @interface NavBarModeOverride {} + /** + * State indicating that this sender device is close to a receiver device, so the user can + * potentially *start* a cast to the receiver device if the user moves their device a bit + * closer. + * <p> + * Important notes: + * <ul> + * <li>This state represents that the device is close enough to inform the user that + * transferring is an option, but the device is *not* close enough to actually initiate a + * transfer yet.</li> + * <li>This state is for *starting* a cast. It should be used when this device is currently + * playing media locally and the media should be transferred to be played on the receiver + * device instead.</li> + * </ul> + * + * @hide + */ + @SystemApi + public static final int MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST = 0; + + /** + * State indicating that this sender device is close to a receiver device, so the user can + * potentially *end* a cast on the receiver device if the user moves this device a bit closer. + * <p> + * Important notes: + * <ul> + * <li>This state represents that the device is close enough to inform the user that + * transferring is an option, but the device is *not* close enough to actually initiate a + * transfer yet.</li> + * <li>This state is for *ending* a cast. It should be used when media is currently being + * played on the receiver device and the media should be transferred to play locally + * instead.</li> + * </ul> + * + * @hide + */ + @SystemApi + public static final int MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST = 1; + + /** + * State indicating that a media transfer from this sender device to a receiver device has been + * started. + * <p> + * Important note: This state is for *starting* a cast. It should be used when this device is + * currently playing media locally and the media has started being transferred to the receiver + * device instead. + * + * @hide + */ + @SystemApi + public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED = 2; + + /** + * State indicating that a media transfer from the receiver and back to this sender device + * has been started. + * <p> + * Important note: This state is for *ending* a cast. It should be used when media is currently + * being played on the receiver device and the media has started being transferred to play + * locally instead. + * + * @hide + */ + @SystemApi + public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED = 3; + + /** + * State indicating that a media transfer from this sender device to a receiver device has + * finished successfully. + * <p> + * Important note: This state is for *starting* a cast. It should be used when this device had + * previously been playing media locally and the media has successfully been transferred to the + * receiver device instead. + * + * @hide + */ + @SystemApi + public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED = 4; + + /** + * State indicating that a media transfer from the receiver and back to this sender device has + * finished successfully. + * <p> + * Important note: This state is for *ending* a cast. It should be used when media was + * previously being played on the receiver device and has been successfully transferred to play + * locally on this device instead. + * + * @hide + */ + @SystemApi + public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED = 5; + + /** + * State indicating that the attempted transfer to the receiver device has failed. + * + * @hide + */ + @SystemApi + public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED = 6; + + /** + * State indicating that the attempted transfer back to this device has failed. + * + * @hide + */ + @SystemApi + public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED = 7; + + /** + * State indicating that this sender device is no longer close to the receiver device. + * + * @hide + */ + @SystemApi + public static final int MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER = 8; + + /** @hide */ + @IntDef(prefix = {"MEDIA_TRANSFER_SENDER_STATE_"}, value = { + MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, + MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, + MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED, + MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface MediaTransferSenderState {} + + /** + * State indicating that this receiver device is close to a sender device, so the user can + * potentially start or end a cast to the receiver device if the user moves the sender device a + * bit closer. + * <p> + * Important note: This state represents that the device is close enough to inform the user that + * transferring is an option, but the device is *not* close enough to actually initiate a + * transfer yet. + * + * @hide + */ + @SystemApi + public static final int MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER = 0; + + /** + * State indicating that this receiver device is no longer close to the sender device. + * + * @hide + */ + @SystemApi + public static final int MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER = 1; + + /** @hide */ + @IntDef(prefix = {"MEDIA_TRANSFER_RECEIVER_STATE_"}, value = { + MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, + MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface MediaTransferReceiverState {} + @UnsupportedAppUsage private Context mContext; private IStatusBarService mService; @@ -789,6 +952,81 @@ public class StatusBarManager { return navBarModeOverride; } + /** + * Notifies the system of a new media tap-to-transfer state for the <b>sender</b> device. + * + * <p>The callback should only be provided for the {@link + * MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED} or {@link + * MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED} states, since those are the + * only states where an action can be un-done. + * + * @param displayState the new state for media tap-to-transfer. + * @param routeInfo the media route information for the media being transferred. + * @param undoExecutor an executor to run the callback on and must be provided if the + * callback is non-null. + * @param undoCallback a callback that will be triggered if the user elects to undo a media + * transfer. + * + * @throws IllegalArgumentException if an undo callback is provided for states that are not a + * succeeded state. + * @throws IllegalArgumentException if an executor is not provided when a callback is. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) + public void updateMediaTapToTransferSenderDisplay( + @MediaTransferSenderState int displayState, + @NonNull MediaRoute2Info routeInfo, + @Nullable Executor undoExecutor, + @Nullable Runnable undoCallback + ) { + Objects.requireNonNull(routeInfo); + if (displayState != MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED + && displayState != MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED + && undoCallback != null) { + throw new IllegalArgumentException( + "The undoCallback should only be provided when the state is a " + + "transfer succeeded state"); + } + if (undoCallback != null && undoExecutor == null) { + throw new IllegalArgumentException( + "You must pass an executor when you pass an undo callback"); + } + IStatusBarService svc = getService(); + try { + UndoCallback callbackProxy = null; + if (undoExecutor != null) { + callbackProxy = new UndoCallback(undoExecutor, undoCallback); + } + svc.updateMediaTapToTransferSenderDisplay(displayState, routeInfo, callbackProxy); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Notifies the system of a new media tap-to-transfer state for the <b>receiver</b> device. + * + * @param displayState the new state for media tap-to-transfer. + * @param routeInfo the media route information for the media being transferred. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) + public void updateMediaTapToTransferReceiverDisplay( + @MediaTransferReceiverState int displayState, + @NonNull MediaRoute2Info routeInfo) { + Objects.requireNonNull(routeInfo); + IStatusBarService svc = getService(); + try { + svc.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + /** @hide */ public static String windowStateToString(int state) { if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING"; @@ -1071,4 +1309,29 @@ public class StatusBarManager { mExecutor.execute(() -> mCallback.accept(userResponse)); } } + + /** + * @hide + */ + static final class UndoCallback extends IUndoMediaTransferCallback.Stub { + @NonNull + private final Executor mExecutor; + @NonNull + private final Runnable mCallback; + + UndoCallback(@NonNull Executor executor, @NonNull Runnable callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onUndoTriggered() { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(mCallback); + } finally { + restoreCallingIdentity(callingIdentity); + } + } + } } diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index c12e8195eeb4..d6d3a97687b5 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -25,6 +25,7 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.content.Context; import android.content.pm.PackageManager; +import android.graphics.Point; import android.hardware.CameraStatus; import android.hardware.ICameraService; import android.hardware.ICameraServiceListener; @@ -458,12 +459,14 @@ public final class CameraManager { (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); if (display != null) { - int width = display.getWidth(); - int height = display.getHeight(); + Point sz = new Point(); + display.getRealSize(sz); + int width = sz.x; + int height = sz.y; if (height > width) { height = width; - width = display.getHeight(); + width = sz.y; } ret = new Size(width, height); @@ -471,7 +474,7 @@ public final class CameraManager { Log.e(TAG, "Invalid default display!"); } } catch (Exception e) { - Log.e(TAG, "getDisplaySize Failed. " + e.toString()); + Log.e(TAG, "getDisplaySize Failed. " + e); } return ret; diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 1a7a63ae8b69..af8ec279ac30 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -916,6 +916,17 @@ public final class DisplayManagerGlobal { } /** + * Returns the system preferred display mode. + */ + public Display.Mode getSystemPreferredDisplayMode(int displayId) { + try { + return mDm.getSystemPreferredDisplayMode(displayId); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** * When enabled the app requested display resolution and refresh rate is always selected * in DisplayModeDirector regardless of user settings and policies for low brightness, low * battery etc. diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 35663af189f4..b3af52b19063 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -168,6 +168,7 @@ interface IDisplayManager { // Requires MODIFY_USER_PREFERRED_DISPLAY_MODE permission. void setUserPreferredDisplayMode(int displayId, in Mode mode); Mode getUserPreferredDisplayMode(int displayId); + Mode getSystemPreferredDisplayMode(int displayId); // When enabled the app requested display resolution and refresh rate is always selected // in DisplayModeDirector regardless of user settings and policies for low brightness, low diff --git a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java index e7d76f66230d..bb3464691bcb 100644 --- a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java +++ b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java @@ -142,7 +142,7 @@ public final class HdmiAudioSystemClient extends HdmiClient { * @hide */ public void setSystemAudioMode(boolean state, @NonNull SetSystemAudioModeCallback callback) { - // TODO(amyjojo): implement this when needed. + // TODO(b/217509829): implement this when needed. } /** diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index 1b7c00c00f83..63306612fdaf 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -526,12 +526,15 @@ public final class BinderProxy implements IBinder { public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { Binder.checkParcel(this, code, data, "Unreasonably large binder buffer"); - if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0) + boolean warnOnBlocking = mWarnOnBlocking; // Cache it to reduce volatile access. + + if (warnOnBlocking && ((flags & FLAG_ONEWAY) == 0) && Binder.sWarnOnBlockingOnCurrentThread.get()) { // For now, avoid spamming the log by disabling after we've logged // about this interface at least once mWarnOnBlocking = false; + warnOnBlocking = false; if (Build.IS_USERDEBUG) { // Log this as a WTF on userdebug builds. @@ -578,7 +581,13 @@ public final class BinderProxy implements IBinder { } try { - return transactNative(code, data, reply, flags); + final boolean result = transactNative(code, data, reply, flags); + + if (reply != null && !warnOnBlocking) { + reply.addFlags(Parcel.FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT); + } + + return result; } finally { AppOpsManager.resumeNotedAppOpsCollection(prevCollection); diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 35b9ccc83e0b..9970641dd324 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -31,6 +31,7 @@ import android.sysprop.DeviceProperties; import android.sysprop.SocProperties; import android.sysprop.TelephonyProperties; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Slog; import android.view.View; @@ -39,6 +40,7 @@ import dalvik.system.VMRuntime; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; /** @@ -396,6 +398,17 @@ public class Build { */ public static final String CODENAME = getString("ro.build.version.codename"); + /** + * All known codenames starting from {@link VERSION_CODES.Q}. + * + * <p>This includes in development codenames as well. + * + * @hide + */ + @SystemApi + @NonNull public static final Set<String> KNOWN_CODENAMES = + new ArraySet<>(new String[]{"Q", "R", "S", "Sv2", "Tiramisu"}); + private static final String[] ALL_CODENAMES = getStringList("ro.build.version.all_codenames", ","); diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 321b3643b45b..9998e1206602 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -18,6 +18,7 @@ package android.os; import static java.util.Objects.requireNonNull; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -53,6 +54,8 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.io.Serializable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -229,6 +232,25 @@ public final class Parcel { private RuntimeException mStack; + /** @hide */ + @TestApi + public static final int FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT = 1 << 0; + + /** @hide */ + @TestApi + public static final int FLAG_PROPAGATE_ALLOW_BLOCKING = 1 << 1; + + /** @hide */ + @IntDef(flag = true, prefix = { "FLAG_" }, value = { + FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT, + FLAG_PROPAGATE_ALLOW_BLOCKING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ParcelFlags {} + + @ParcelFlags + private int mFlags; + /** * Whether or not to parcel the stack trace of an exception. This has a performance * impact, so should only be included in specific processes and only on debug builds. @@ -585,6 +607,40 @@ public final class Parcel { nativeMarkForBinder(mNativePtr, binder); } + /** @hide */ + @ParcelFlags + @TestApi + public int getFlags() { + return mFlags; + } + + /** @hide */ + public void setFlags(@ParcelFlags int flags) { + mFlags = flags; + } + + /** @hide */ + public void addFlags(@ParcelFlags int flags) { + mFlags |= flags; + } + + /** @hide */ + private boolean hasFlags(@ParcelFlags int flags) { + return (mFlags & flags) == flags; + } + + /** + * This method is used by the AIDL compiler for system components. Not intended to be + * used by non-system apps. + */ + // Note: Ideally this method should be @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES), + // but we need to make this method public due to the way the aidl compiler is compiled. + // We don't really need to protect it; even if 3p / non-system apps, nothing would happen. + // This would only work when used on a reply parcel by a binder object that's allowed-blocking. + public void setPropagateAllowBlocking() { + addFlags(FLAG_PROPAGATE_ALLOW_BLOCKING); + } + /** * Returns the total amount of data contained in the parcel. */ @@ -3045,7 +3101,15 @@ public final class Parcel { * Read an object from the parcel at the current dataPosition(). */ public final IBinder readStrongBinder() { - return nativeReadStrongBinder(mNativePtr); + final IBinder result = nativeReadStrongBinder(mNativePtr); + + // If it's a reply from a method with @PropagateAllowBlocking, then inherit allow-blocking + // from the object that returned it. + if (result != null && hasFlags( + FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT | FLAG_PROPAGATE_ALLOW_BLOCKING)) { + Binder.allowBlocking(result); + } + return result; } /** @@ -4995,6 +5059,7 @@ public final class Parcel { } private void freeBuffer() { + mFlags = 0; resetSqaushingState(); if (mOwnsNativeParcelObject) { nativeFreeBuffer(mNativePtr); diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index fa39380cdcc1..246a8c9d17d3 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1118,6 +1118,19 @@ public final class Display { } /** + * Returns the system's preferred display mode. This mode will be used when the user has not + * specified a display-mode preference. This returns null if the boot display mode feature is + * not supported by system. + * + * @hide + */ + @TestApi + @Nullable + public Display.Mode getSystemPreferredDisplayMode() { + return mGlobal.getSystemPreferredDisplayMode(getDisplayId()); + } + + /** * Returns the display's HDR capabilities. * * @see #isHdr() diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 904d7c82959b..6f5fea258119 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -197,6 +197,9 @@ public final class SurfaceControl implements Parcelable { private static native int[] nativeGetCompositionDataspaces(); private static native boolean nativeSetActiveColorMode(IBinder displayToken, int colorMode); + private static native boolean nativeGetBootDisplayModeSupport(); + private static native void nativeSetBootDisplayMode(IBinder displayToken, int displayMode); + private static native void nativeClearBootDisplayMode(IBinder displayToken); private static native void nativeSetAutoLowLatencyMode(IBinder displayToken, boolean on); private static native void nativeSetGameContentType(IBinder displayToken, boolean on); private static native void nativeSetDisplayPowerMode( @@ -1878,6 +1881,8 @@ public final class SurfaceControl implements Parcelable { public boolean autoLowLatencyModeSupported; public boolean gameContentTypeSupported; + public int preferredBootDisplayMode; + @Override public String toString() { return "DynamicDisplayInfo{" @@ -1887,7 +1892,8 @@ public final class SurfaceControl implements Parcelable { + ", activeColorMode=" + activeColorMode + ", hdrCapabilities=" + hdrCapabilities + ", autoLowLatencyModeSupported=" + autoLowLatencyModeSupported - + ", gameContentTypeSupported" + gameContentTypeSupported + "}"; + + ", gameContentTypeSupported" + gameContentTypeSupported + + ", preferredBootDisplayMode" + preferredBootDisplayMode + "}"; } @Override @@ -1899,7 +1905,8 @@ public final class SurfaceControl implements Parcelable { && activeDisplayModeId == that.activeDisplayModeId && Arrays.equals(supportedColorModes, that.supportedColorModes) && activeColorMode == that.activeColorMode - && Objects.equals(hdrCapabilities, that.hdrCapabilities); + && Objects.equals(hdrCapabilities, that.hdrCapabilities) + && preferredBootDisplayMode == that.preferredBootDisplayMode; } @Override @@ -2266,6 +2273,36 @@ public final class SurfaceControl implements Parcelable { /** * @hide */ + public static boolean getBootDisplayModeSupport() { + return nativeGetBootDisplayModeSupport(); + } + + /** There is no associated getter for this method. When this is set, the display is expected + * to start up in this mode next time the device reboots. + * @hide + */ + public static void setBootDisplayMode(IBinder displayToken, int displayModeId) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + + nativeSetBootDisplayMode(displayToken, displayModeId); + } + + /** + * @hide + */ + public static void clearBootDisplayMode(IBinder displayToken) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + + nativeClearBootDisplayMode(displayToken); + } + + /** + * @hide + */ public static void setAutoLowLatencyMode(IBinder displayToken, boolean on) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 347153c7e53e..cdb69e546b8f 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1011,7 +1011,9 @@ public class ResolverActivity extends Activity implements protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); ViewPager viewPager = findViewById(R.id.profile_pager); - outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem()); + if (viewPager != null) { + outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem()); + } } @Override @@ -1019,7 +1021,9 @@ public class ResolverActivity extends Activity implements super.onRestoreInstanceState(savedInstanceState); resetButtonBar(); ViewPager viewPager = findViewById(R.id.profile_pager); - viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY)); + if (viewPager != null) { + viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY)); + } mMultiProfilePagerAdapter.clearInactiveProfileCache(); } @@ -1568,6 +1572,11 @@ public class ResolverActivity extends Activity implements rebuildCompleted = rebuildCompleted && rebuildInactiveCompleted; } + if (shouldUseMiniResolver()) { + configureMiniResolverContent(); + return false; + } + if (useLayoutWithDefault()) { mLayoutId = R.layout.resolver_list_with_default; } else { @@ -1578,6 +1587,72 @@ public class ResolverActivity extends Activity implements return postRebuildList(rebuildCompleted); } + private void configureMiniResolverContent() { + mLayoutId = R.layout.miniresolver; + setContentView(mLayoutId); + + DisplayResolveInfo sameProfileResolveInfo = + mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList.get(0); + boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK; + + DisplayResolveInfo otherProfileResolveInfo = + mMultiProfilePagerAdapter.getInactiveListAdapter().mDisplayList.get(0); + ImageView icon = findViewById(R.id.icon); + // TODO: Set icon drawable to app icon. + + ((TextView) findViewById(R.id.open_cross_profile)).setText( + getResources().getString( + inWorkProfile ? R.string.miniresolver_open_in_personal + : R.string.miniresolver_open_in_work, + otherProfileResolveInfo.getDisplayLabel())); + ((Button) findViewById(R.id.use_same_profile_browser)).setText( + inWorkProfile ? R.string.miniresolver_use_work_browser + : R.string.miniresolver_use_personal_browser); + + findViewById(R.id.use_same_profile_browser).setOnClickListener( + v -> safelyStartActivity(sameProfileResolveInfo)); + + findViewById(R.id.button_open).setOnClickListener(v -> { + Intent intent = otherProfileResolveInfo.getResolvedIntent(); + if (intent != null) { + prepareIntentForCrossProfileLaunch(intent); + } + safelyStartActivityInternal(otherProfileResolveInfo, + mMultiProfilePagerAdapter.getInactiveListAdapter().mResolverListController + .getUserHandle()); + }); + } + + private boolean shouldUseMiniResolver() { + if (mMultiProfilePagerAdapter.getActiveListAdapter() == null + || mMultiProfilePagerAdapter.getInactiveListAdapter() == null) { + return false; + } + List<DisplayResolveInfo> sameProfileList = + mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList; + List<DisplayResolveInfo> otherProfileList = + mMultiProfilePagerAdapter.getInactiveListAdapter().mDisplayList; + + if (otherProfileList.size() != 1) { + Log.d(TAG, "Found " + otherProfileList.size() + " resolvers in the other profile"); + return false; + } + + if (otherProfileList.get(0).getResolveInfo().handleAllWebDataURI) { + Log.d(TAG, "Other profile is a web browser"); + return false; + } + + for (DisplayResolveInfo info : sameProfileList) { + if (!info.getResolveInfo().handleAllWebDataURI) { + Log.d(TAG, "Non-browser found in this profile"); + return false; + } + } + + return true; + } + /** * Finishing procedures to be performed after the list has been rebuilt. * </p>Subclasses must call postRebuildListInternal at the end of postRebuildList. diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 23ebc9f94915..51eb4296d7ae 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -24,12 +24,14 @@ import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.fingerprint.IUdfpsHbmListener; +import android.media.MediaRoute2Info; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.service.notification.StatusBarNotification; import android.view.InsetsVisibilities; import com.android.internal.statusbar.IAddTileResultCallback; +import com.android.internal.statusbar.IUndoMediaTransferCallback; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.view.AppearanceRegion; @@ -296,4 +298,15 @@ oneway interface IStatusBar void requestAddTile(in ComponentName componentName, in CharSequence appName, in CharSequence label, in Icon icon, in IAddTileResultCallback callback); void cancelRequestAddTile(in String packageName); + + /** Notifies System UI about an update to the media tap-to-transfer sender state. */ + void updateMediaTapToTransferSenderDisplay( + int displayState, + in MediaRoute2Info routeInfo, + in IUndoMediaTransferCallback undoCallback); + + /** Notifies System UI about an update to the media tap-to-transfer receiver state. */ + void updateMediaTapToTransferReceiverDisplay( + int displayState, + in MediaRoute2Info routeInfo); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index f28325e3cc51..0c45e5b3eab4 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -24,6 +24,7 @@ import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.fingerprint.IUdfpsHbmListener; +import android.media.MediaRoute2Info; import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; @@ -33,6 +34,7 @@ import com.android.internal.logging.InstanceId; import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.ISessionListener; import com.android.internal.statusbar.IStatusBar; +import com.android.internal.statusbar.IUndoMediaTransferCallback; import com.android.internal.statusbar.RegisterStatusBarResult; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIconList; @@ -196,4 +198,15 @@ interface IStatusBarService */ void onSessionStarted(int sessionType, in InstanceId instanceId); void onSessionEnded(int sessionType, in InstanceId instanceId); + + /** Notifies System UI about an update to the media tap-to-transfer sender state. */ + void updateMediaTapToTransferSenderDisplay( + int displayState, + in MediaRoute2Info routeInfo, + in IUndoMediaTransferCallback undoCallback); + + /** Notifies System UI about an update to the media tap-to-transfer receiver state. */ + void updateMediaTapToTransferReceiverDisplay( + int displayState, + in MediaRoute2Info routeInfo); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl b/core/java/com/android/internal/statusbar/IUndoMediaTransferCallback.aidl index b47be8736d23..3dd29807be01 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl +++ b/core/java/com/android/internal/statusbar/IUndoMediaTransferCallback.aidl @@ -14,17 +14,16 @@ * limitations under the License. */ -package com.android.systemui.shared.mediattt; +package com.android.internal.statusbar; /** - * An interface that will be invoked by System UI if the user choose to undo a transfer. - * - * Other services will implement this interface and System UI will invoke it. + * An interface that will be invoked if the user chooses to undo a transfer. */ -interface IUndoTransferCallback { +interface IUndoMediaTransferCallback { /** - * Invoked by SystemUI when the user requests to undo the media transfer that just occurred. + * Invoked to notify callers that the user has chosen to undo the media transfer that just + * occurred. * * Implementors of this method are repsonsible for actually undoing the transfer. */ diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 39f17e510a1c..93864fa2e05d 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -237,6 +237,10 @@ public class SystemConfig { // be delivered anonymously even to apps which target O+. final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>(); + // These are the packages that are exempted from the background restriction applied + // by the system automatically, i.e., due to high background current drain. + final ArraySet<String> mBgRestrictionExemption = new ArraySet<>(); + // These are the package names of apps which should be automatically granted domain verification // for all of their domains. The only way these apps can be overridden by the user is by // explicitly disabling overall link handling support in app info. @@ -389,6 +393,10 @@ public class SystemConfig { return mAllowIgnoreLocationSettings; } + public ArraySet<String> getBgRestrictionExemption() { + return mBgRestrictionExemption; + } + public ArraySet<String> getLinkedApps() { return mLinkedApps; } @@ -1049,6 +1057,20 @@ public class SystemConfig { } XmlUtils.skipCurrentTag(parser); } break; + case "bg-restriction-exemption": { + if (allowOverrideAppRestrictions) { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mBgRestrictionExemption.add(pkgname); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; case "default-enabled-vr-app": { if (allowAppConfigs) { String pkgname = parser.getAttributeValue(null, "package"); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index a8cf2536341b..991591369bcb 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -104,6 +104,7 @@ static struct { jfieldID hdrCapabilities; jfieldID autoLowLatencyModeSupported; jfieldID gameContentTypeSupported; + jfieldID preferredBootDisplayMode; } gDynamicDisplayInfoClassInfo; static struct { @@ -1301,6 +1302,9 @@ static jobject nativeGetDynamicDisplayInfo(JNIEnv* env, jclass clazz, jobject to env->SetBooleanField(object, gDynamicDisplayInfoClassInfo.gameContentTypeSupported, info.gameContentTypeSupported); + + env->SetIntField(object, gDynamicDisplayInfoClassInfo.preferredBootDisplayMode, + info.preferredBootDisplayMode); return object; } @@ -1638,6 +1642,27 @@ static void nativeOverrideHdrTypes(JNIEnv* env, jclass clazz, jobject tokenObjec } } +static jboolean nativeGetBootDisplayModeSupport(JNIEnv* env, jclass clazz) { + bool isBootDisplayModeSupported = false; + SurfaceComposerClient::getBootDisplayModeSupport(&isBootDisplayModeSupported); + return static_cast<jboolean>(isBootDisplayModeSupported); +} + +static void nativeSetBootDisplayMode(JNIEnv* env, jclass clazz, jobject tokenObject, + jint displayModId) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObject)); + if (token == NULL) return; + + SurfaceComposerClient::setBootDisplayMode(token, displayModId); +} + +static void nativeClearBootDisplayMode(JNIEnv* env, jclass clazz, jobject tokenObject) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObject)); + if (token == NULL) return; + + SurfaceComposerClient::clearBootDisplayMode(token); +} + static void nativeSetAutoLowLatencyMode(JNIEnv* env, jclass clazz, jobject tokenObject, jboolean on) { sp<IBinder> token(ibinderForJavaObject(env, tokenObject)); if (token == NULL) return; @@ -2046,6 +2071,12 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeGetDisplayNativePrimaries }, {"nativeSetActiveColorMode", "(Landroid/os/IBinder;I)Z", (void*)nativeSetActiveColorMode}, + {"nativeGetBootDisplayModeSupport", "()Z", + (void*)nativeGetBootDisplayModeSupport }, + {"nativeSetBootDisplayMode", "(Landroid/os/IBinder;I)V", + (void*)nativeSetBootDisplayMode }, + {"nativeClearBootDisplayMode", "(Landroid/os/IBinder;)V", + (void*)nativeClearBootDisplayMode }, {"nativeSetAutoLowLatencyMode", "(Landroid/os/IBinder;Z)V", (void*)nativeSetAutoLowLatencyMode }, {"nativeSetGameContentType", "(Landroid/os/IBinder;Z)V", @@ -2184,6 +2215,8 @@ int register_android_view_SurfaceControl(JNIEnv* env) GetFieldIDOrDie(env, dynamicInfoClazz, "autoLowLatencyModeSupported", "Z"); gDynamicDisplayInfoClassInfo.gameContentTypeSupported = GetFieldIDOrDie(env, dynamicInfoClazz, "gameContentTypeSupported", "Z"); + gDynamicDisplayInfoClassInfo.preferredBootDisplayMode = + GetFieldIDOrDie(env, dynamicInfoClazz, "preferredBootDisplayMode", "I"); jclass modeClazz = FindClassOrDie(env, "android/view/SurfaceControl$DisplayMode"); gDisplayModeClassInfo.clazz = MakeGlobalRefOrDie(env, modeClazz); diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml new file mode 100644 index 000000000000..44ed6f2a0676 --- /dev/null +++ b/core/res/res/layout/miniresolver.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<com.android.internal.widget.ResolverDrawerLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:maxWidth="@dimen/resolver_max_width" + android:maxCollapsedHeight="@dimen/resolver_max_collapsed_height" + android:maxCollapsedHeightSmall="56dp" + android:id="@id/contentPanel"> + + <RelativeLayout + android:id="@+id/title_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alwaysShow="true" + android:elevation="@dimen/resolver_elevation" + android:paddingTop="@dimen/resolver_small_margin" + android:paddingStart="@dimen/resolver_edge_margin" + android:paddingEnd="@dimen/resolver_edge_margin" + android:paddingBottom="@dimen/resolver_title_padding_bottom" + android:background="@drawable/bottomsheet_background"> + + <ImageView + android:id="@+id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_centerHorizontal="true" + /> + + <TextView + android:id="@+id/open_cross_profile" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/icon" + android:layout_centerHorizontal="true" + android:textColor="?android:textColorPrimary" + /> + </RelativeLayout> + + <LinearLayout + android:id="@+id/button_bar_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alwaysShow="true" + android:orientation="vertical" + android:background="?attr/colorBackground" + android:layout_ignoreOffset="true"> + <View + android:id="@+id/resolver_button_bar_divider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="?attr/colorBackground" + android:foreground="?attr/dividerVertical" /> + <RelativeLayout + style="?attr/buttonBarStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_ignoreOffset="true" + android:layout_hasNestedScrollIndicator="true" + android:gravity="end|center_vertical" + android:orientation="horizontal" + android:layoutDirection="locale" + android:measureWithLargestChild="true" + android:paddingTop="@dimen/resolver_button_bar_spacing" + android:paddingBottom="@dimen/resolver_button_bar_spacing" + android:paddingStart="@dimen/resolver_edge_margin" + android:paddingEnd="@dimen/resolver_small_margin" + android:elevation="@dimen/resolver_elevation"> + + <Button + android:id="@+id/use_same_profile_browser" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:maxLines="2" + style="@android:style/Widget.DeviceDefault.Button.Borderless" + android:fontFamily="@android:string/config_headlineFontFamilyMedium" + android:textAllCaps="false" + android:text="@string/activity_resolver_use_once" + /> + + <Button + android:id="@+id/button_open" + android:layout_width="wrap_content" + android:layout_alignParentEnd="true" + android:maxLines="2" + style="@android:style/Widget.DeviceDefault.Button.Colored" + android:fontFamily="@android:string/config_headlineFontFamilyMedium" + android:textAllCaps="false" + android:layout_height="wrap_content" + android:text="@string/whichViewApplicationLabel" + /> + </RelativeLayout> + </LinearLayout> +</com.android.internal.widget.ResolverDrawerLayout> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 902d5e0b9f32..53cf4639f11b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2376,6 +2376,12 @@ <!-- ComponentNames of the dreams that we should hide --> <string-array name="config_disabledDreamComponents" translatable="false"> </string-array> + <!-- The list of supported dream complications --> + <integer-array name="config_supportedDreamComplications"> + </integer-array> + <!-- The list of dream complications which should be enabled by default --> + <integer-array name="config_dreamComplicationsEnabledByDefault"> + </integer-array> <!-- Are we allowed to dream while not plugged in? --> <bool name="config_dreamsEnabledOnBattery">false</bool> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 2e4b783a6dca..52c62050f6a1 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5936,9 +5936,9 @@ <string name="resolver_no_personal_apps_available">No personal apps</string> <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] --> - <string name="miniresolver_open_in_personal">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in personal profile?</string> + <string name="miniresolver_open_in_personal">Open <xliff:g id="app" example="YouTube">%s</xliff:g> in your personal profile?</string> <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] --> - <string name="miniresolver_open_in_work">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in work profile?</string> + <string name="miniresolver_open_in_work">Open <xliff:g id="app" example="YouTube">%s</xliff:g> in your work profile?</string> <!-- Button option. Open the link in the personal browser. [CHAR LIMIT=NONE] --> <string name="miniresolver_use_personal_browser">Use personal browser</string> <!-- Button option. Open the link in the work browser. [CHAR LIMIT=NONE] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 30a196395b35..facfdb22b91b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1599,6 +1599,13 @@ <java-symbol type="layout" name="resolver_list_per_profile" /> <java-symbol type="layout" name="chooser_list_per_profile" /> <java-symbol type="layout" name="resolver_empty_states" /> + <java-symbol type="id" name="open_cross_profile" /> + <java-symbol type="string" name="miniresolver_open_in_personal" /> + <java-symbol type="string" name="miniresolver_open_in_work" /> + <java-symbol type="string" name="miniresolver_use_personal_browser" /> + <java-symbol type="string" name="miniresolver_use_work_browser" /> + <java-symbol type="id" name="button_open" /> + <java-symbol type="id" name="use_same_profile_browser" /> <java-symbol type="anim" name="slide_in_child_bottom" /> <java-symbol type="anim" name="slide_in_right" /> @@ -2221,6 +2228,8 @@ <java-symbol type="integer" name="config_dreamsBatteryLevelMinimumWhenNotPowered" /> <java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" /> <java-symbol type="string" name="config_dreamsDefaultComponent" /> + <java-symbol type="array" name="config_supportedDreamComplications" /> + <java-symbol type="array" name="config_dreamComplicationsEnabledByDefault" /> <java-symbol type="drawable" name="default_dream_preview" /> <java-symbol type="array" name="config_disabledDreamComponents" /> <java-symbol type="string" name="config_dozeComponent" /> @@ -2713,6 +2722,7 @@ <java-symbol type="bool" name="config_allow_ussd_over_ims" /> <java-symbol type="attr" name="touchscreenBlocksFocus" /> <java-symbol type="layout" name="resolver_list_with_default" /> + <java-symbol type="layout" name="miniresolver" /> <java-symbol type="string" name="activity_resolver_use_always" /> <java-symbol type="string" name="whichApplicationNamed" /> <java-symbol type="string" name="whichApplicationLabel" /> diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 75d20252ae27..2817728fd9ea 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -731,6 +731,25 @@ public class ResolverActivityTest { } @Test + public void testMiniResolver() { + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(1); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(1); + // Personal profile only has a browser + personalResolvedComponentInfos.get(0).getResolveInfoAt(0).handleAllWebDataURI = true; + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createSendImageIntent(); + sendIntent.setType("TestType"); + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + onView(withId(R.id.open_cross_profile)).check(matches(isDisplayed())); + } + + @Test public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 15a398de9021..b8333fb57848 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -5919,13 +5919,14 @@ public class AudioManager { * @param newDevice Bluetooth device connected or null if there is no new devices * @param previousDevice Bluetooth device disconnected or null if there is no disconnected * devices - * @param info contain all info related to the device. {@link BtProfileConnectionInfo} + * @param info contain all info related to the device. {@link BluetoothProfileConnectionInfo} * {@hide} */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(android.Manifest.permission.BLUETOOTH_STACK) public void handleBluetoothActiveDeviceChanged(@Nullable BluetoothDevice newDevice, - @Nullable BluetoothDevice previousDevice, @NonNull BtProfileConnectionInfo info) { + @Nullable BluetoothDevice previousDevice, + @NonNull BluetoothProfileConnectionInfo info) { final IAudioService service = getService(); try { service.handleBluetoothActiveDeviceChanged(newDevice, previousDevice, info); diff --git a/media/java/android/media/BtProfileConnectionInfo.aidl b/media/java/android/media/BluetoothProfileConnectionInfo.aidl index 047f06be0964..0617084fd826 100644 --- a/media/java/android/media/BtProfileConnectionInfo.aidl +++ b/media/java/android/media/BluetoothProfileConnectionInfo.aidl @@ -16,5 +16,5 @@ package android.media; -parcelable BtProfileConnectionInfo; +parcelable BluetoothProfileConnectionInfo; diff --git a/media/java/android/media/BtProfileConnectionInfo.java b/media/java/android/media/BluetoothProfileConnectionInfo.java index 88b9777e911d..c14884657ddd 100644 --- a/media/java/android/media/BtProfileConnectionInfo.java +++ b/media/java/android/media/BluetoothProfileConnectionInfo.java @@ -26,15 +26,14 @@ import android.os.Parcelable; * {@hide} */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) -public final class BtProfileConnectionInfo implements Parcelable { - +public final class BluetoothProfileConnectionInfo implements Parcelable { private final int mProfile; private final boolean mSupprNoisy; private final int mVolume; private final boolean mIsLeOutput; - private BtProfileConnectionInfo(int profile, boolean suppressNoisyIntent, int volume, - boolean isLeOutput) { + private BluetoothProfileConnectionInfo(int profile, boolean suppressNoisyIntent, + int volume, boolean isLeOutput) { mProfile = profile; mSupprNoisy = suppressNoisyIntent; mVolume = volume; @@ -45,21 +44,21 @@ public final class BtProfileConnectionInfo implements Parcelable { * Constructor used by BtHelper when a profile is connected * {@hide} */ - public BtProfileConnectionInfo(int profile) { + public BluetoothProfileConnectionInfo(int profile) { this(profile, false, -1, false); } - public static final @NonNull Parcelable.Creator<BtProfileConnectionInfo> CREATOR = - new Parcelable.Creator<BtProfileConnectionInfo>() { + public static final @NonNull Parcelable.Creator<BluetoothProfileConnectionInfo> CREATOR = + new Parcelable.Creator<BluetoothProfileConnectionInfo>() { @Override - public BtProfileConnectionInfo createFromParcel(Parcel source) { - return new BtProfileConnectionInfo(source.readInt(), source.readBoolean(), - source.readInt(), source.readBoolean()); + public BluetoothProfileConnectionInfo createFromParcel(Parcel source) { + return new BluetoothProfileConnectionInfo(source.readInt(), + source.readBoolean(), source.readInt(), source.readBoolean()); } @Override - public BtProfileConnectionInfo[] newArray(int size) { - return new BtProfileConnectionInfo[size]; + public BluetoothProfileConnectionInfo[] newArray(int size) { + return new BluetoothProfileConnectionInfo[size]; } }; @@ -84,10 +83,10 @@ public final class BtProfileConnectionInfo implements Parcelable { * * @param volume of device -1 to ignore value */ - public static @NonNull BtProfileConnectionInfo a2dpInfo(boolean suppressNoisyIntent, - int volume) { - return new BtProfileConnectionInfo(BluetoothProfile.A2DP, suppressNoisyIntent, volume, - false); + public static @NonNull BluetoothProfileConnectionInfo createA2dpInfo( + boolean suppressNoisyIntent, int volume) { + return new BluetoothProfileConnectionInfo(BluetoothProfile.A2DP, suppressNoisyIntent, + volume, false); } /** @@ -96,8 +95,8 @@ public final class BtProfileConnectionInfo implements Parcelable { * * @param volume of device -1 to ignore value */ - public static @NonNull BtProfileConnectionInfo a2dpSinkInfo(int volume) { - return new BtProfileConnectionInfo(BluetoothProfile.A2DP_SINK, true, volume, false); + public static @NonNull BluetoothProfileConnectionInfo createA2dpSinkInfo(int volume) { + return new BluetoothProfileConnectionInfo(BluetoothProfile.A2DP_SINK, true, volume, false); } /** @@ -106,9 +105,10 @@ public final class BtProfileConnectionInfo implements Parcelable { * @param suppressNoisyIntent if true the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} * intent will not be sent. */ - public static @NonNull BtProfileConnectionInfo hearingAidInfo(boolean suppressNoisyIntent) { - return new BtProfileConnectionInfo(BluetoothProfile.HEARING_AID, suppressNoisyIntent, -1, - false); + public static @NonNull BluetoothProfileConnectionInfo createHearingAidInfo( + boolean suppressNoisyIntent) { + return new BluetoothProfileConnectionInfo(BluetoothProfile.HEARING_AID, suppressNoisyIntent, + -1, false); } /** @@ -119,10 +119,10 @@ public final class BtProfileConnectionInfo implements Parcelable { * * @param isLeOutput if true mean the device is an output device, if false it's an input device */ - public static @NonNull BtProfileConnectionInfo leAudio(boolean suppressNoisyIntent, - boolean isLeOutput) { - return new BtProfileConnectionInfo(BluetoothProfile.LE_AUDIO, suppressNoisyIntent, -1, - isLeOutput); + public static @NonNull BluetoothProfileConnectionInfo createLeAudioInfo( + boolean suppressNoisyIntent, boolean isLeOutput) { + return new BluetoothProfileConnectionInfo(BluetoothProfile.LE_AUDIO, suppressNoisyIntent, + -1, isLeOutput); } /** @@ -136,7 +136,7 @@ public final class BtProfileConnectionInfo implements Parcelable { * @return {@code true} if {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent will not be * sent */ - public boolean getSuppressNoisyIntent() { + public boolean isSuppressNoisyIntent() { return mSupprNoisy; } @@ -153,7 +153,7 @@ public final class BtProfileConnectionInfo implements Parcelable { * @return {@code true} is the LE device is an output device, {@code false} if it's an input * device */ - public boolean getIsLeOutput() { + public boolean isLeOutput() { return mIsLeOutput; } } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 96199a988704..4451c64b6548 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -25,7 +25,7 @@ import android.media.AudioFocusInfo; import android.media.AudioPlaybackConfiguration; import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; -import android.media.BtProfileConnectionInfo; +import android.media.BluetoothProfileConnectionInfo; import android.media.IAudioFocusDispatcher; import android.media.IAudioModeDispatcher; import android.media.IAudioRoutesObserver; @@ -276,7 +276,7 @@ interface IAudioService { oneway void playerHasOpPlayAudio(in int piid, in boolean hasOpPlayAudio); void handleBluetoothActiveDeviceChanged(in BluetoothDevice newDevice, - in BluetoothDevice previousDevice, in BtProfileConnectionInfo info); + in BluetoothDevice previousDevice, in BluetoothProfileConnectionInfo info); oneway void setFocusRequestResultFromExtPolicy(in AudioFocusInfo afi, int requestResult, in IAudioPolicyCallback pcb); diff --git a/media/java/android/media/tv/tuner/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java index 0f5bf082d44f..14a9144c9cdc 100644 --- a/media/java/android/media/tv/tuner/DemuxCapabilities.java +++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java @@ -36,13 +36,8 @@ import java.lang.annotation.RetentionPolicy; public class DemuxCapabilities { /** @hide */ - @IntDef(flag = true, value = { - Filter.TYPE_TS, - Filter.TYPE_MMTP, - Filter.TYPE_IP, - Filter.TYPE_TLV, - Filter.TYPE_ALP - }) + @IntDef(value = {Filter.TYPE_TS, Filter.TYPE_MMTP, Filter.TYPE_IP, Filter.TYPE_TLV, + Filter.TYPE_ALP}) @Retention(RetentionPolicy.SOURCE) public @interface FilterCapabilities {} diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java index 9f4423644877..14accaafe189 100644 --- a/media/java/android/media/tv/tuner/filter/Filter.java +++ b/media/java/android/media/tv/tuner/filter/Filter.java @@ -153,8 +153,8 @@ public class Filter implements AutoCloseable { /** @hide */ - @IntDef(flag = true, prefix = "STATUS_", value = {STATUS_DATA_READY, STATUS_LOW_WATER, - STATUS_HIGH_WATER, STATUS_OVERFLOW}) + @IntDef(prefix = "STATUS_", + value = {STATUS_DATA_READY, STATUS_LOW_WATER, STATUS_HIGH_WATER, STATUS_OVERFLOW}) @Retention(RetentionPolicy.SOURCE) public @interface Status {} @@ -185,8 +185,7 @@ public class Filter implements AutoCloseable { public static final int STATUS_OVERFLOW = DemuxFilterStatus.OVERFLOW; /** @hide */ - @IntDef(flag = true, - prefix = "SCRAMBLING_STATUS_", + @IntDef(prefix = "SCRAMBLING_STATUS_", value = {SCRAMBLING_STATUS_UNKNOWN, SCRAMBLING_STATUS_NOT_SCRAMBLED, SCRAMBLING_STATUS_SCRAMBLED}) @Retention(RetentionPolicy.SOURCE) @@ -209,8 +208,7 @@ public class Filter implements AutoCloseable { android.hardware.tv.tuner.ScramblingStatus.SCRAMBLED; /** @hide */ - @IntDef(flag = true, - prefix = "MONITOR_EVENT_", + @IntDef(prefix = "MONITOR_EVENT_", value = {MONITOR_EVENT_SCRAMBLING_STATUS, MONITOR_EVENT_IP_CID_CHANGE}) @Retention(RetentionPolicy.SOURCE) public @interface MonitorEventMask {} diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java index d34581da29cb..b16d9fb247b7 100644 --- a/media/java/android/media/tv/tuner/filter/RecordSettings.java +++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java @@ -40,8 +40,7 @@ public class RecordSettings extends Settings { * * @hide */ - @IntDef(flag = true, - value = {TS_INDEX_INVALID, TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR, + @IntDef(value = {TS_INDEX_INVALID, TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR, TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED, TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, TS_INDEX_DISCONTINUITY_INDICATOR, TS_INDEX_RANDOM_ACCESS_INDICATOR, TS_INDEX_PRIORITY_INDICATOR, @@ -165,7 +164,6 @@ public class RecordSettings extends Settings { * @hide */ @IntDef(prefix = "SC_INDEX_", - flag = true, value = {SC_INDEX_I_FRAME, SC_INDEX_P_FRAME, SC_INDEX_B_FRAME, SC_INDEX_SEQUENCE, SC_INDEX_I_SLICE, SC_INDEX_P_SLICE, SC_INDEX_B_SLICE, SC_INDEX_SI_SLICE, SC_INDEX_SP_SLICE}) @@ -214,8 +212,7 @@ public class RecordSettings extends Settings { * * @hide */ - @IntDef(flag = true, - value = {SC_HEVC_INDEX_SPS, SC_HEVC_INDEX_AUD, SC_HEVC_INDEX_SLICE_CE_BLA_W_LP, + @IntDef(value = {SC_HEVC_INDEX_SPS, SC_HEVC_INDEX_AUD, SC_HEVC_INDEX_SLICE_CE_BLA_W_LP, SC_HEVC_INDEX_SLICE_BLA_W_RADL, SC_HEVC_INDEX_SLICE_BLA_N_LP, SC_HEVC_INDEX_SLICE_IDR_W_RADL, SC_HEVC_INDEX_SLICE_IDR_N_LP, SC_HEVC_INDEX_SLICE_TRAIL_CRA}) @@ -258,8 +255,7 @@ public class RecordSettings extends Settings { /** * @hide */ - @IntDef(flag = true, - prefix = "SC_", + @IntDef(prefix = "SC_", value = { SC_INDEX_I_FRAME, SC_INDEX_P_FRAME, diff --git a/media/java/android/media/tv/tuner/filter/SharedFilter.java b/media/java/android/media/tv/tuner/filter/SharedFilter.java index 740ab9c45c83..21964ee5a32a 100644 --- a/media/java/android/media/tv/tuner/filter/SharedFilter.java +++ b/media/java/android/media/tv/tuner/filter/SharedFilter.java @@ -38,7 +38,7 @@ import java.util.concurrent.Executor; @SystemApi public final class SharedFilter implements AutoCloseable { /** @hide */ - @IntDef(flag = true, prefix = "STATUS_", value = {STATUS_INACCESSIBLE}) + @IntDef(prefix = "STATUS_", value = {STATUS_INACCESSIBLE}) @Retention(RetentionPolicy.SOURCE) public @interface Status {} diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java index e0405ef57f2e..6c1134ae8fac 100644 --- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java @@ -36,8 +36,7 @@ import java.lang.annotation.RetentionPolicy; @SystemApi public class AnalogFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "SIGNAL_TYPE_", + @IntDef(prefix = "SIGNAL_TYPE_", value = {SIGNAL_TYPE_UNDEFINED, SIGNAL_TYPE_AUTO, SIGNAL_TYPE_PAL, SIGNAL_TYPE_PAL_M, SIGNAL_TYPE_PAL_N, SIGNAL_TYPE_PAL_60, SIGNAL_TYPE_NTSC, SIGNAL_TYPE_NTSC_443, SIGNAL_TYPE_SECAM}) @@ -82,8 +81,7 @@ public class AnalogFrontendSettings extends FrontendSettings { public static final int SIGNAL_TYPE_SECAM = FrontendAnalogType.SECAM; /** @hide */ - @IntDef(flag = true, - prefix = "SIF_", + @IntDef(prefix = "SIF_", value = {SIF_UNDEFINED, SIF_AUTO, SIF_BG, SIF_BG_A2, SIF_BG_NICAM, SIF_I, SIF_DK, SIF_DK1_A2, SIF_DK2_A2, SIF_DK3_A2, SIF_DK_NICAM, SIF_L, SIF_M, SIF_M_BTSC, SIF_M_A2, SIF_M_EIAJ, SIF_I_NICAM, SIF_L_NICAM, SIF_L_PRIME}) diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java index a7157e20c5e6..c99f911c4236 100644 --- a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java @@ -39,8 +39,7 @@ import java.lang.annotation.RetentionPolicy; public class Atsc3FrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "BANDWIDTH_", + @IntDef(prefix = "BANDWIDTH_", value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_BANDWIDTH_6MHZ, BANDWIDTH_BANDWIDTH_7MHZ, BANDWIDTH_BANDWIDTH_8MHZ}) @Retention(RetentionPolicy.SOURCE) @@ -69,8 +68,7 @@ public class Atsc3FrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "MODULATION_", + @IntDef(prefix = "MODULATION_", value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_QPSK, MODULATION_MOD_16QAM, MODULATION_MOD_64QAM, MODULATION_MOD_256QAM, @@ -113,8 +111,7 @@ public class Atsc3FrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "TIME_INTERLEAVE_MODE_", + @IntDef(prefix = "TIME_INTERLEAVE_MODE_", value = {TIME_INTERLEAVE_MODE_UNDEFINED, TIME_INTERLEAVE_MODE_AUTO, TIME_INTERLEAVE_MODE_CTI, TIME_INTERLEAVE_MODE_HTI}) @Retention(RetentionPolicy.SOURCE) @@ -140,8 +137,7 @@ public class Atsc3FrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "CODERATE_", + @IntDef(prefix = "CODERATE_", value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_2_15, CODERATE_3_15, CODERATE_4_15, CODERATE_5_15, CODERATE_6_15, CODERATE_7_15, CODERATE_8_15, CODERATE_9_15, CODERATE_10_15, CODERATE_11_15, CODERATE_12_15, CODERATE_13_15}) @@ -207,8 +203,7 @@ public class Atsc3FrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "FEC_", + @IntDef(prefix = "FEC_", value = {FEC_UNDEFINED, FEC_AUTO, FEC_BCH_LDPC_16K, FEC_BCH_LDPC_64K, FEC_CRC_LDPC_16K, FEC_CRC_LDPC_64K, FEC_LDPC_16K, FEC_LDPC_64K}) @Retention(RetentionPolicy.SOURCE) @@ -249,8 +244,7 @@ public class Atsc3FrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "DEMOD_OUTPUT_FORMAT_", + @IntDef(prefix = "DEMOD_OUTPUT_FORMAT_", value = {DEMOD_OUTPUT_FORMAT_UNDEFINED, DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET, DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET}) @Retention(RetentionPolicy.SOURCE) diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java index 3071ce861e0d..64c6ce629740 100644 --- a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java @@ -34,8 +34,7 @@ import java.lang.annotation.RetentionPolicy; public class AtscFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "MODULATION_", + @IntDef(prefix = "MODULATION_", value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_8VSB, MODULATION_MOD_16VSB}) @Retention(RetentionPolicy.SOURCE) diff --git a/media/java/android/media/tv/tuner/frontend/DtmbFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DtmbFrontendSettings.java index 6b5d6ca9eb84..07c1fbffd91e 100644 --- a/media/java/android/media/tv/tuner/frontend/DtmbFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/DtmbFrontendSettings.java @@ -43,8 +43,7 @@ import java.lang.annotation.RetentionPolicy; public final class DtmbFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "BANDWIDTH_", + @IntDef(prefix = "BANDWIDTH_", value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_6MHZ, BANDWIDTH_8MHZ}) @Retention(RetentionPolicy.SOURCE) public @interface Bandwidth {} @@ -68,8 +67,7 @@ public final class DtmbFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "TIME_INTERLEAVE_MODE_", + @IntDef(prefix = "TIME_INTERLEAVE_MODE_", value = {TIME_INTERLEAVE_MODE_UNDEFINED, TIME_INTERLEAVE_MODE_AUTO, TIME_INTERLEAVE_MODE_TIMER_INT_240, TIME_INTERLEAVE_MODE_TIMER_INT_720}) @Retention(RetentionPolicy.SOURCE) @@ -97,8 +95,7 @@ public final class DtmbFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "GUARD_INTERVAL_", + @IntDef(prefix = "GUARD_INTERVAL_", value = {GUARD_INTERVAL_UNDEFINED, GUARD_INTERVAL_AUTO, GUARD_INTERVAL_PN_420_VARIOUS, GUARD_INTERVAL_PN_595_CONST, GUARD_INTERVAL_PN_945_VARIOUS, GUARD_INTERVAL_PN_420_CONST, @@ -143,8 +140,7 @@ public final class DtmbFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "MODULATION_", + @IntDef(prefix = "MODULATION_", value = {MODULATION_CONSTELLATION_UNDEFINED, MODULATION_CONSTELLATION_AUTO, MODULATION_CONSTELLATION_4QAM, MODULATION_CONSTELLATION_4QAM_NR, MODULATION_CONSTELLATION_16QAM, MODULATION_CONSTELLATION_32QAM, @@ -187,8 +183,7 @@ public final class DtmbFrontendSettings extends FrontendSettings { FrontendDtmbModulation.CONSTELLATION_64QAM; /** @hide */ - @IntDef(flag = true, - prefix = "CODERATE_", + @IntDef(prefix = "CODERATE_", value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_2_5, CODERATE_3_5, CODERATE_4_5}) @Retention(RetentionPolicy.SOURCE) public @interface CodeRate {} @@ -215,8 +210,7 @@ public final class DtmbFrontendSettings extends FrontendSettings { public static final int CODERATE_4_5 = FrontendDtmbCodeRate.CODERATE_4_5; /** @hide */ - @IntDef(flag = true, - prefix = "TRANSMISSION_MODE_", + @IntDef(prefix = "TRANSMISSION_MODE_", value = {TRANSMISSION_MODE_UNDEFINED, TRANSMISSION_MODE_AUTO, TRANSMISSION_MODE_C1, TRANSMISSION_MODE_C3780}) @Retention(RetentionPolicy.SOURCE) diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java index afe953de5389..45bfc09b8ae7 100644 --- a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java @@ -40,8 +40,7 @@ import java.lang.annotation.RetentionPolicy; public class DvbcFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "MODULATION_", + @IntDef(prefix = "MODULATION_", value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_16QAM, MODULATION_MOD_32QAM, MODULATION_MOD_64QAM, MODULATION_MOD_128QAM, MODULATION_MOD_256QAM}) @@ -98,8 +97,7 @@ public class DvbcFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "ANNEX_", + @IntDef(prefix = "ANNEX_", value = {ANNEX_UNDEFINED, ANNEX_A, ANNEX_B, ANNEX_C}) @Retention(RetentionPolicy.SOURCE) public @interface Annex {} @@ -159,8 +157,7 @@ public class DvbcFrontendSettings extends FrontendSettings { android.hardware.tv.tuner.FrontendSpectralInversion.INVERTED; /** @hide */ - @IntDef(flag = true, - prefix = "TIME_INTERLEAVE_MODE_", + @IntDef(prefix = "TIME_INTERLEAVE_MODE_", value = {TIME_INTERLEAVE_MODE_UNDEFINED, TIME_INTERLEAVE_MODE_AUTO, TIME_INTERLEAVE_MODE_128_1_0, TIME_INTERLEAVE_MODE_128_1_1, TIME_INTERLEAVE_MODE_64_2, TIME_INTERLEAVE_MODE_32_4, @@ -226,8 +223,7 @@ public class DvbcFrontendSettings extends FrontendSettings { FrontendCableTimeInterleaveMode.INTERLEAVING_128_4; /** @hide */ - @IntDef(flag = true, - prefix = "BANDWIDTH_", + @IntDef(prefix = "BANDWIDTH_", value = {BANDWIDTH_UNDEFINED, BANDWIDTH_5MHZ, BANDWIDTH_6MHZ, BANDWIDTH_7MHZ, BANDWIDTH_8MHZ}) @Retention(RetentionPolicy.SOURCE) diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java index e16f19285100..56dbb480880b 100644 --- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java @@ -42,8 +42,7 @@ import java.lang.annotation.RetentionPolicy; @SystemApi public class DvbsFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "SCAN_TYPE_", + @IntDef(prefix = "SCAN_TYPE_", value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_DIRECT, SCAN_TYPE_DISEQC, SCAN_TYPE_UNICABLE, SCAN_TYPE_JESS}) @Retention(RetentionPolicy.SOURCE) @@ -75,8 +74,7 @@ public class DvbsFrontendSettings extends FrontendSettings { public static final int SCAN_TYPE_JESS = FrontendDvbsScanType.JESS; /** @hide */ - @IntDef(flag = true, - prefix = "MODULATION_", + @IntDef(prefix = "MODULATION_", value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_QPSK, MODULATION_MOD_8PSK, MODULATION_MOD_16QAM, MODULATION_MOD_16PSK, MODULATION_MOD_32PSK, MODULATION_MOD_ACM, MODULATION_MOD_8APSK, @@ -207,8 +205,7 @@ public class DvbsFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "STANDARD_", + @IntDef(prefix = "STANDARD_", value = {STANDARD_AUTO, STANDARD_S, STANDARD_S2, STANDARD_S2X}) @Retention(RetentionPolicy.SOURCE) public @interface Standard {} diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java index d86e9a8af5f6..06547e1fc0e7 100644 --- a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java @@ -42,8 +42,7 @@ import java.lang.annotation.RetentionPolicy; public class DvbtFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "TRANSMISSION_MODE_", + @IntDef(prefix = "TRANSMISSION_MODE_", value = {TRANSMISSION_MODE_UNDEFINED, TRANSMISSION_MODE_AUTO, TRANSMISSION_MODE_2K, TRANSMISSION_MODE_8K, TRANSMISSION_MODE_4K, TRANSMISSION_MODE_1K, TRANSMISSION_MODE_16K, TRANSMISSION_MODE_32K}) @@ -98,8 +97,7 @@ public class DvbtFrontendSettings extends FrontendSettings { FrontendDvbtTransmissionMode.MODE_32K_E; /** @hide */ - @IntDef(flag = true, - prefix = "BANDWIDTH_", + @IntDef(prefix = "BANDWIDTH_", value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_8MHZ, BANDWIDTH_7MHZ, BANDWIDTH_6MHZ, BANDWIDTH_5MHZ, BANDWIDTH_1_7MHZ, BANDWIDTH_10MHZ}) @Retention(RetentionPolicy.SOURCE) @@ -140,8 +138,7 @@ public class DvbtFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "CONSTELLATION_", + @IntDef(prefix = "CONSTELLATION_", value = {CONSTELLATION_UNDEFINED, CONSTELLATION_AUTO, CONSTELLATION_QPSK, CONSTELLATION_16QAM, CONSTELLATION_64QAM, CONSTELLATION_256QAM, CONSTELLATION_QPSK_R, CONSTELLATION_16QAM_R, CONSTELLATION_64QAM_R, @@ -192,8 +189,7 @@ public class DvbtFrontendSettings extends FrontendSettings { FrontendDvbtConstellation.CONSTELLATION_256QAM_R; /** @hide */ - @IntDef(flag = true, - prefix = "HIERARCHY_", + @IntDef(prefix = "HIERARCHY_", value = {HIERARCHY_UNDEFINED, HIERARCHY_AUTO, HIERARCHY_NON_NATIVE, HIERARCHY_1_NATIVE, HIERARCHY_2_NATIVE, HIERARCHY_4_NATIVE, HIERARCHY_NON_INDEPTH, HIERARCHY_1_INDEPTH, HIERARCHY_2_INDEPTH, HIERARCHY_4_INDEPTH}) @@ -243,8 +239,7 @@ public class DvbtFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "CODERATE_", + @IntDef(prefix = "CODERATE_", value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_2, CODERATE_2_3, CODERATE_3_4, CODERATE_5_6, CODERATE_7_8, CODERATE_3_5, CODERATE_4_5, CODERATE_6_7, CODERATE_8_9}) @Retention(RetentionPolicy.SOURCE) @@ -296,8 +291,7 @@ public class DvbtFrontendSettings extends FrontendSettings { public static final int CODERATE_8_9 = FrontendDvbtCoderate.CODERATE_8_9; /** @hide */ - @IntDef(flag = true, - prefix = "GUARD_INTERVAL_", + @IntDef(prefix = "GUARD_INTERVAL_", value = {GUARD_INTERVAL_UNDEFINED, GUARD_INTERVAL_AUTO, GUARD_INTERVAL_1_32, GUARD_INTERVAL_1_16, GUARD_INTERVAL_1_8, GUARD_INTERVAL_1_4, @@ -346,8 +340,7 @@ public class DvbtFrontendSettings extends FrontendSettings { public static final int GUARD_INTERVAL_19_256 = FrontendDvbtGuardInterval.INTERVAL_19_256; /** @hide */ - @IntDef(flag = true, - prefix = "STANDARD", + @IntDef(prefix = "STANDARD_", value = {STANDARD_AUTO, STANDARD_T, STANDARD_T2} ) @Retention(RetentionPolicy.SOURCE) diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java index 38bffec3f77f..2f45a7072017 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java @@ -89,8 +89,7 @@ public abstract class FrontendSettings { /** @hide */ - @LongDef(flag = true, - prefix = "FEC_", + @LongDef(prefix = "FEC_", value = {FEC_UNDEFINED, FEC_AUTO, FEC_1_2, FEC_1_3, FEC_1_4, FEC_1_5, FEC_2_3, FEC_2_5, FEC_2_9, FEC_3_4, FEC_3_5, FEC_4_5, FEC_4_15, FEC_5_6, FEC_5_9, FEC_6_7, FEC_7_8, FEC_7_9, FEC_7_15, FEC_8_9, FEC_8_15, FEC_9_10, FEC_9_20, FEC_11_15, FEC_11_20, diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java index 726fe15b8edb..7e83d1545032 100644 --- a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java @@ -36,8 +36,7 @@ import java.lang.annotation.RetentionPolicy; @SystemApi public class Isdbs3FrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "MODULATION_", + @IntDef(prefix = "MODULATION_", value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_BPSK, MODULATION_MOD_QPSK, MODULATION_MOD_8PSK, MODULATION_MOD_16APSK, MODULATION_MOD_32APSK}) @@ -75,8 +74,7 @@ public class Isdbs3FrontendSettings extends FrontendSettings { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, - prefix = "CODERATE_", + @IntDef(prefix = "CODERATE_", value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_3, CODERATE_2_5, CODERATE_1_2, CODERATE_3_5, CODERATE_2_3, CODERATE_3_4, CODERATE_7_9, CODERATE_4_5, CODERATE_5_6, CODERATE_7_8, CODERATE_9_10}) diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java index 51ec5aeeb2eb..50294539e8f8 100644 --- a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java @@ -54,8 +54,7 @@ public class IsdbsFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "MODULATION_", + @IntDef(prefix = "MODULATION_", value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_BPSK, MODULATION_MOD_QPSK, MODULATION_MOD_TC8PSK}) @Retention(RetentionPolicy.SOURCE) @@ -84,8 +83,7 @@ public class IsdbsFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "CODERATE_", + @IntDef(prefix = "CODERATE_", value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_2, CODERATE_2_3, CODERATE_3_4, CODERATE_5_6, CODERATE_7_8}) @Retention(RetentionPolicy.SOURCE) diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java index 89512a05674b..f08a51497f75 100644 --- a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java +++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java @@ -40,8 +40,7 @@ import java.lang.annotation.RetentionPolicy; @SystemApi public class IsdbtFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "MODULATION_", + @IntDef(prefix = "MODULATION_", value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_DQPSK, MODULATION_MOD_QPSK, MODULATION_MOD_16QAM, MODULATION_MOD_64QAM}) @Retention(RetentionPolicy.SOURCE) @@ -74,8 +73,7 @@ public class IsdbtFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "MODE_", + @IntDef(prefix = "MODE_", value = {MODE_UNDEFINED, MODE_AUTO, MODE_1, MODE_2, MODE_3}) @Retention(RetentionPolicy.SOURCE) public @interface Mode {} @@ -103,8 +101,7 @@ public class IsdbtFrontendSettings extends FrontendSettings { /** @hide */ - @IntDef(flag = true, - prefix = "BANDWIDTH_", + @IntDef(prefix = "BANDWIDTH_", value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_8MHZ, BANDWIDTH_7MHZ, BANDWIDTH_6MHZ}) @Retention(RetentionPolicy.SOURCE) @@ -132,7 +129,7 @@ public class IsdbtFrontendSettings extends FrontendSettings { public static final int BANDWIDTH_6MHZ = FrontendIsdbtBandwidth.BANDWIDTH_6MHZ; /** @hide */ - @IntDef(flag = true, prefix = "PARTIAL_RECEPTION_FLAG_", + @IntDef(prefix = "PARTIAL_RECEPTION_FLAG_", value = {PARTIAL_RECEPTION_FLAG_UNDEFINED, PARTIAL_RECEPTION_FLAG_FALSE, PARTIAL_RECEPTION_FLAG_TRUE}) @Retention(RetentionPolicy.SOURCE) @@ -153,7 +150,7 @@ public class IsdbtFrontendSettings extends FrontendSettings { public static final int PARTIAL_RECEPTION_FLAG_TRUE = FrontendIsdbtPartialReceptionFlag.TRUE; /** @hide */ - @IntDef(flag = true, prefix = "TIME_INTERLEAVE_MODE_", + @IntDef(prefix = "TIME_INTERLEAVE_MODE_", value = {TIME_INTERLEAVE_MODE_UNDEFINED, TIME_INTERLEAVE_MODE_AUTO, TIME_INTERLEAVE_MODE_1_0, TIME_INTERLEAVE_MODE_1_4, TIME_INTERLEAVE_MODE_1_8, TIME_INTERLEAVE_MODE_1_16, TIME_INTERLEAVE_MODE_2_0, TIME_INTERLEAVE_MODE_2_2, diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BtProfileConnectionInfoTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java index fd66d3b9904e..f23794b50543 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BtProfileConnectionInfoTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java @@ -19,7 +19,7 @@ package com.android.mediaframeworktest.unit; import static org.junit.Assert.assertEquals; import android.bluetooth.BluetoothProfile; -import android.media.BtProfileConnectionInfo; +import android.media.BluetoothProfileConnectionInfo; import androidx.test.runner.AndroidJUnit4; @@ -27,22 +27,24 @@ import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) -public class BtProfileConnectionInfoTest { +public class BluetoothProfileConnectionInfoTest { @Test public void testCoverageA2dp() { final boolean supprNoisy = false; final int volume = 42; - final BtProfileConnectionInfo info = BtProfileConnectionInfo.a2dpInfo(supprNoisy, volume); + final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo + .createA2dpInfo(supprNoisy, volume); assertEquals(info.getProfile(), BluetoothProfile.A2DP); - assertEquals(info.getSuppressNoisyIntent(), supprNoisy); + assertEquals(info.isSuppressNoisyIntent(), supprNoisy); assertEquals(info.getVolume(), volume); } @Test public void testCoverageA2dpSink() { final int volume = 42; - final BtProfileConnectionInfo info = BtProfileConnectionInfo.a2dpSinkInfo(volume); + final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo + .createA2dpSinkInfo(volume); assertEquals(info.getProfile(), BluetoothProfile.A2DP_SINK); assertEquals(info.getVolume(), volume); } @@ -50,20 +52,21 @@ public class BtProfileConnectionInfoTest { @Test public void testCoveragehearingAid() { final boolean supprNoisy = true; - final BtProfileConnectionInfo info = BtProfileConnectionInfo.hearingAidInfo(supprNoisy); + final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo + .createHearingAidInfo(supprNoisy); assertEquals(info.getProfile(), BluetoothProfile.HEARING_AID); - assertEquals(info.getSuppressNoisyIntent(), supprNoisy); + assertEquals(info.isSuppressNoisyIntent(), supprNoisy); } @Test public void testCoverageLeAudio() { final boolean supprNoisy = false; final boolean isLeOutput = true; - final BtProfileConnectionInfo info = BtProfileConnectionInfo.leAudio(supprNoisy, - isLeOutput); + final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo + .createLeAudioInfo(supprNoisy, isLeOutput); assertEquals(info.getProfile(), BluetoothProfile.LE_AUDIO); - assertEquals(info.getSuppressNoisyIntent(), supprNoisy); - assertEquals(info.getIsLeOutput(), isLeOutput); + assertEquals(info.isSuppressNoisyIntent(), supprNoisy); + assertEquals(info.isLeOutput(), isLeOutput); } } diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index f9a17746892e..b7beb6e1a6a8 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -309,6 +309,9 @@ LIBANDROID { android_res_nquery; # introduced=29 android_res_nresult; # introduced=29 android_res_nsend; # introduced=29 + android_tag_socket_with_uid; # introduced=Tiramisu + android_tag_socket; # introduced=Tiramisu + android_untag_socket; # introduced=Tiramisu AThermal_acquireManager; # introduced=30 AThermal_releaseManager; # introduced=30 AThermal_getCurrentThermalStatus; # introduced=30 diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp index 327b1fb2f5d8..54538d91a5cf 100644 --- a/packages/ConnectivityT/framework-t/Android.bp +++ b/packages/ConnectivityT/framework-t/Android.bp @@ -125,14 +125,14 @@ filegroup { name: "framework-connectivity-ethernet-sources", srcs: [ "src/android/net/EthernetManager.java", + "src/android/net/EthernetNetworkManagementException.java", + "src/android/net/EthernetNetworkManagementException.aidl", "src/android/net/EthernetNetworkSpecifier.java", + "src/android/net/EthernetNetworkUpdateRequest.java", + "src/android/net/EthernetNetworkUpdateRequest.aidl", "src/android/net/IEthernetManager.aidl", + "src/android/net/IEthernetNetworkManagementListener.aidl", "src/android/net/IEthernetServiceListener.aidl", - "src/android/net/IInternalNetworkManagementListener.aidl", - "src/android/net/InternalNetworkUpdateRequest.java", - "src/android/net/InternalNetworkUpdateRequest.aidl", - "src/android/net/InternalNetworkManagementException.java", - "src/android/net/InternalNetworkManagementException.aidl", "src/android/net/ITetheredInterfaceCallback.aidl", ], path: "src", diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java index ece54df96665..f472d563c477 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java +++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java @@ -320,15 +320,15 @@ public class EthernetManager { } private static final class InternalNetworkManagementListener - extends IInternalNetworkManagementListener.Stub { + extends IEthernetNetworkManagementListener.Stub { @NonNull private final Executor mExecutor; @NonNull - private final BiConsumer<Network, InternalNetworkManagementException> mListener; + private final BiConsumer<Network, EthernetNetworkManagementException> mListener; InternalNetworkManagementListener( @NonNull final Executor executor, - @NonNull final BiConsumer<Network, InternalNetworkManagementException> listener) { + @NonNull final BiConsumer<Network, EthernetNetworkManagementException> listener) { Objects.requireNonNull(executor, "Pass a non-null executor"); Objects.requireNonNull(listener, "Pass a non-null listener"); mExecutor = executor; @@ -338,14 +338,14 @@ public class EthernetManager { @Override public void onComplete( @Nullable final Network network, - @Nullable final InternalNetworkManagementException e) { + @Nullable final EthernetNetworkManagementException e) { mExecutor.execute(() -> mListener.accept(network, e)); } } private InternalNetworkManagementListener getInternalNetworkManagementListener( @Nullable final Executor executor, - @Nullable final BiConsumer<Network, InternalNetworkManagementException> listener) { + @Nullable final BiConsumer<Network, EthernetNetworkManagementException> listener) { if (null != listener) { Objects.requireNonNull(executor, "Pass a non-null executor, or a null listener"); } @@ -360,9 +360,9 @@ public class EthernetManager { private void updateConfiguration( @NonNull String iface, - @NonNull InternalNetworkUpdateRequest request, + @NonNull EthernetNetworkUpdateRequest request, @Nullable @CallbackExecutor Executor executor, - @Nullable BiConsumer<Network, InternalNetworkManagementException> listener) { + @Nullable BiConsumer<Network, EthernetNetworkManagementException> listener) { final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener( executor, listener); try { @@ -375,7 +375,7 @@ public class EthernetManager { private void connectNetwork( @NonNull String iface, @Nullable @CallbackExecutor Executor executor, - @Nullable BiConsumer<Network, InternalNetworkManagementException> listener) { + @Nullable BiConsumer<Network, EthernetNetworkManagementException> listener) { final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener( executor, listener); try { @@ -388,7 +388,7 @@ public class EthernetManager { private void disconnectNetwork( @NonNull String iface, @Nullable @CallbackExecutor Executor executor, - @Nullable BiConsumer<Network, InternalNetworkManagementException> listener) { + @Nullable BiConsumer<Network, EthernetNetworkManagementException> listener) { final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener( executor, listener); try { diff --git a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.aidl index dcce706989f6..adf9e5a4db9d 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.aidl @@ -16,4 +16,4 @@ package android.net; - parcelable InternalNetworkManagementException;
\ No newline at end of file + parcelable EthernetNetworkManagementException;
\ No newline at end of file diff --git a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.java index 798e9c3b52b5..a35f28e172fd 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java +++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.java @@ -23,11 +23,11 @@ import android.os.Parcelable; import java.util.Objects; /** @hide */ -public final class InternalNetworkManagementException +public final class EthernetNetworkManagementException extends RuntimeException implements Parcelable { /* @hide */ - public InternalNetworkManagementException(@NonNull final String errorMessage) { + public EthernetNetworkManagementException(@NonNull final String errorMessage) { super(errorMessage); } @@ -40,7 +40,7 @@ public final class InternalNetworkManagementException public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; - final InternalNetworkManagementException that = (InternalNetworkManagementException) obj; + final EthernetNetworkManagementException that = (EthernetNetworkManagementException) obj; return Objects.equals(getMessage(), that.getMessage()); } @@ -56,16 +56,16 @@ public final class InternalNetworkManagementException } @NonNull - public static final Parcelable.Creator<InternalNetworkManagementException> CREATOR = - new Parcelable.Creator<InternalNetworkManagementException>() { + public static final Parcelable.Creator<EthernetNetworkManagementException> CREATOR = + new Parcelable.Creator<EthernetNetworkManagementException>() { @Override - public InternalNetworkManagementException[] newArray(int size) { - return new InternalNetworkManagementException[size]; + public EthernetNetworkManagementException[] newArray(int size) { + return new EthernetNetworkManagementException[size]; } @Override - public InternalNetworkManagementException createFromParcel(@NonNull Parcel source) { - return new InternalNetworkManagementException(source.readString()); + public EthernetNetworkManagementException createFromParcel(@NonNull Parcel source) { + return new EthernetNetworkManagementException(source.readString()); } }; } diff --git a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.aidl index da00cb97afb4..debc348ea363 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.aidl @@ -16,4 +16,4 @@ package android.net; - parcelable InternalNetworkUpdateRequest;
\ No newline at end of file + parcelable EthernetNetworkUpdateRequest;
\ No newline at end of file diff --git a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java index f42c4b7c420d..4d229d23b163 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java +++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java @@ -23,7 +23,7 @@ import android.os.Parcelable; import java.util.Objects; /** @hide */ -public final class InternalNetworkUpdateRequest implements Parcelable { +public final class EthernetNetworkUpdateRequest implements Parcelable { @NonNull private final StaticIpConfiguration mIpConfig; @NonNull @@ -40,7 +40,7 @@ public final class InternalNetworkUpdateRequest implements Parcelable { } /** @hide */ - public InternalNetworkUpdateRequest(@NonNull final StaticIpConfiguration ipConfig, + public EthernetNetworkUpdateRequest(@NonNull final StaticIpConfiguration ipConfig, @NonNull final NetworkCapabilities networkCapabilities) { Objects.requireNonNull(ipConfig); Objects.requireNonNull(networkCapabilities); @@ -48,7 +48,7 @@ public final class InternalNetworkUpdateRequest implements Parcelable { mNetworkCapabilities = new NetworkCapabilities(networkCapabilities); } - private InternalNetworkUpdateRequest(@NonNull final Parcel source) { + private EthernetNetworkUpdateRequest(@NonNull final Parcel source) { Objects.requireNonNull(source); mIpConfig = StaticIpConfiguration.CREATOR.createFromParcel(source); mNetworkCapabilities = NetworkCapabilities.CREATOR.createFromParcel(source); @@ -56,7 +56,7 @@ public final class InternalNetworkUpdateRequest implements Parcelable { @Override public String toString() { - return "InternalNetworkUpdateRequest{" + return "EthernetNetworkUpdateRequest{" + "mIpConfig=" + mIpConfig + ", mNetworkCapabilities=" + mNetworkCapabilities + '}'; } @@ -65,7 +65,7 @@ public final class InternalNetworkUpdateRequest implements Parcelable { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - InternalNetworkUpdateRequest that = (InternalNetworkUpdateRequest) o; + EthernetNetworkUpdateRequest that = (EthernetNetworkUpdateRequest) o; return Objects.equals(that.getIpConfig(), mIpConfig) && Objects.equals(that.getNetworkCapabilities(), mNetworkCapabilities); @@ -88,16 +88,16 @@ public final class InternalNetworkUpdateRequest implements Parcelable { } @NonNull - public static final Parcelable.Creator<InternalNetworkUpdateRequest> CREATOR = - new Parcelable.Creator<InternalNetworkUpdateRequest>() { + public static final Parcelable.Creator<EthernetNetworkUpdateRequest> CREATOR = + new Parcelable.Creator<EthernetNetworkUpdateRequest>() { @Override - public InternalNetworkUpdateRequest[] newArray(int size) { - return new InternalNetworkUpdateRequest[size]; + public EthernetNetworkUpdateRequest[] newArray(int size) { + return new EthernetNetworkUpdateRequest[size]; } @Override - public InternalNetworkUpdateRequest createFromParcel(@NonNull Parcel source) { - return new InternalNetworkUpdateRequest(source); + public EthernetNetworkUpdateRequest createFromParcel(@NonNull Parcel source) { + return new EthernetNetworkUpdateRequest(source); } }; } diff --git a/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl index e688bea1cfac..544d02ba76ff 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl @@ -18,8 +18,8 @@ package android.net; import android.net.IpConfiguration; import android.net.IEthernetServiceListener; -import android.net.IInternalNetworkManagementListener; -import android.net.InternalNetworkUpdateRequest; +import android.net.IEthernetNetworkManagementListener; +import android.net.EthernetNetworkUpdateRequest; import android.net.ITetheredInterfaceCallback; /** @@ -38,8 +38,8 @@ interface IEthernetManager void setIncludeTestInterfaces(boolean include); void requestTetheredInterface(in ITetheredInterfaceCallback callback); void releaseTetheredInterface(in ITetheredInterfaceCallback callback); - void updateConfiguration(String iface, in InternalNetworkUpdateRequest request, - in IInternalNetworkManagementListener listener); - void connectNetwork(String iface, in IInternalNetworkManagementListener listener); - void disconnectNetwork(String iface, in IInternalNetworkManagementListener listener); + void updateConfiguration(String iface, in EthernetNetworkUpdateRequest request, + in IEthernetNetworkManagementListener listener); + void connectNetwork(String iface, in IEthernetNetworkManagementListener listener); + void disconnectNetwork(String iface, in IEthernetNetworkManagementListener listener); } diff --git a/packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetNetworkManagementListener.aidl index 69cde3bd14e8..93edccfdafd9 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetNetworkManagementListener.aidl @@ -16,10 +16,10 @@ package android.net; -import android.net.InternalNetworkManagementException; +import android.net.EthernetNetworkManagementException; import android.net.Network; /** @hide */ -oneway interface IInternalNetworkManagementListener { - void onComplete(in Network network, in InternalNetworkManagementException exception); +oneway interface IEthernetNetworkManagementListener { + void onComplete(in Network network, in EthernetNetworkManagementException exception); }
\ No newline at end of file diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml index b150e0169a96..45253bb7944a 100644 --- a/packages/SettingsLib/res/values/config.xml +++ b/packages/SettingsLib/res/values/config.xml @@ -28,9 +28,4 @@ <!-- Control whether status bar should distinguish HSPA data icon form UMTS data icon on devices --> <bool name="config_hspa_data_distinguishable">false</bool> - - <integer-array name="config_supportedDreamComplications"> - </integer-array> - <integer-array name="config_dreamComplicationsEnabledByDefault"> - </integer-array> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index 99e3160fcbe3..15ca8cdf75ac 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -173,8 +173,10 @@ public class A2dpProfile implements LocalBluetoothProfile { } public BluetoothDevice getActiveDevice() { - if (mService == null) return null; - return mService.getActiveDevice(); + if (mBluetoothAdapter == null) return null; + final List<BluetoothDevice> activeDevices = mBluetoothAdapter + .getActiveDevices(BluetoothProfile.A2DP); + return (activeDevices.size() > 0) ? activeDevices.get(0) : null; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index b11bbdec191f..7e5c1240cc63 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -132,10 +132,12 @@ public class HeadsetProfile implements LocalBluetoothProfile { } public BluetoothDevice getActiveDevice() { - if (mService == null) { + if (mBluetoothAdapter == null) { return null; } - return mService.getActiveDevice(); + final List<BluetoothDevice> activeDevices = mBluetoothAdapter + .getActiveDevices(BluetoothProfile.HEADSET); + return (activeDevices.size() > 0) ? activeDevices.get(0) : null; } public int getAudioState(BluetoothDevice device) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index dc109cac37b2..6f2d4decf033 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -173,8 +173,10 @@ public class HearingAidProfile implements LocalBluetoothProfile { } public List<BluetoothDevice> getActiveDevices() { - if (mService == null) return new ArrayList<>(); - return mService.getActiveDevices(); + if (mBluetoothAdapter == null) { + return new ArrayList<>(); + } + return mBluetoothAdapter.getActiveDevices(BluetoothProfile.HEARING_AID); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java index 209507ac7088..db6d41ef692d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java @@ -21,12 +21,12 @@ import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_ALL; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; -import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothCodecConfig; import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.content.Context; @@ -177,10 +177,10 @@ public class LeAudioProfile implements LocalBluetoothProfile { } public List<BluetoothDevice> getActiveDevices() { - if (mService == null) { + if (mBluetoothAdapter == null) { return new ArrayList<>(); } - return mService.getActiveDevices(); + return mBluetoothAdapter.getActiveDevices(BluetoothProfile.LE_AUDIO); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index 6bf43e528009..a000c099347d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -151,13 +151,13 @@ public class DreamBackend { .map(ComponentName::unflattenFromString) .collect(Collectors.toSet()); - mSupportedComplications = - Arrays.stream(resources.getIntArray(R.array.config_supportedDreamComplications)) - .boxed() - .collect(Collectors.toSet()); + mSupportedComplications = Arrays.stream(resources.getIntArray( + com.android.internal.R.array.config_supportedDreamComplications)) + .boxed() + .collect(Collectors.toSet()); - mDefaultEnabledComplications = Arrays.stream( - resources.getIntArray(R.array.config_dreamComplicationsEnabledByDefault)) + mDefaultEnabledComplications = Arrays.stream(resources.getIntArray( + com.android.internal.R.array.config_dreamComplicationsEnabledByDefault)) .boxed() // A complication can only be enabled by default if it is also supported. .filter(mSupportedComplications::contains) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java index f167721f94bf..d7b366e60a1d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java @@ -60,6 +60,8 @@ public class A2dpProfileTest { private BluetoothDevice mDevice; @Mock private BluetoothA2dp mBluetoothA2dp; + @Mock + private BluetoothAdapter mBluetoothAdapter; private BluetoothProfile.ServiceListener mServiceListener; private A2dpProfile mProfile; @@ -72,7 +74,8 @@ public class A2dpProfileTest { mProfile = new A2dpProfile(mContext, mDeviceManager, mProfileManager); mServiceListener = mShadowBluetoothAdapter.getServiceListener(); mServiceListener.onServiceConnected(BluetoothProfile.A2DP, mBluetoothA2dp); - when(mBluetoothA2dp.getActiveDevice()).thenReturn(mDevice); + when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.A2DP))) + .thenReturn(Arrays.asList(mDevice)); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java index 53d465305a69..86f7850cf1f2 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java @@ -24,8 +24,6 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; -import com.android.settingslib.R; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -53,10 +51,15 @@ public final class DreamBackendTest { final Resources res = mock(Resources.class); when(mContext.getResources()).thenReturn(res); - when(res.getIntArray(R.array.config_supportedDreamComplications)).thenReturn( + when(res.getIntArray( + com.android.internal.R.array.config_supportedDreamComplications)).thenReturn( SUPPORTED_DREAM_COMPLICATIONS); - when(res.getIntArray(R.array.config_dreamComplicationsEnabledByDefault)).thenReturn( + when(res.getIntArray( + com.android.internal.R.array.config_dreamComplicationsEnabledByDefault)).thenReturn( DEFAULT_DREAM_COMPLICATIONS); + when(res.getStringArray( + com.android.internal.R.array.config_disabledDreamComponents)).thenReturn( + new String[]{}); mBackend = new DreamBackend(mContext); } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index f83431b58c62..776a5117cb2e 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -873,12 +873,6 @@ android:singleUser="true" android:permission="android.permission.BIND_DREAM_SERVICE" /> - <!-- Service for external clients to do media transfer --> - <!-- TODO(b/203800643): Export and guard with a permission. --> - <service - android:name=".media.taptotransfer.sender.MediaTttSenderService" - /> - <!-- Service for external clients to notify us of nearby media devices --> <!-- TODO(b/216313420): Export and guard with a permission. --> <service diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml index ecb3cb3961a4..339cab42048b 100644 --- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml +++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml @@ -19,8 +19,10 @@ <com.android.systemui.qs.FooterActionsView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="48dp" - android:gravity="center_vertical"> + android:layout_height="@dimen/qs_footer_height" + android:gravity="center_vertical" + android:layout_gravity="bottom" +> <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch" diff --git a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml new file mode 100644 index 000000000000..95bdd8948c7c --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** Copyright 2022, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +--> + +<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc --> +<com.android.systemui.qs.FooterActionsView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_footer_height" + android:gravity="center_vertical" + android:layout_gravity="bottom" +> + + <View + android:layout_height="1dp" + android:layout_width="0dp" + android:layout_weight="1" + /> + + <LinearLayout + android:layout_height="match_parent" + android:layout_width="wrap_content" + > + + <com.android.systemui.statusbar.phone.MultiUserSwitch + android:id="@+id/multi_user_switch" + android:layout_width="@dimen/qs_footer_action_button_size" + android:layout_height="@dimen/qs_footer_action_button_size" + android:layout_marginEnd="@dimen/qs_tile_margin_horizontal" + android:background="@drawable/qs_footer_action_circle" + android:focusable="true"> + + <ImageView + android:id="@+id/multi_user_avatar" + android:layout_width="@dimen/multi_user_avatar_expanded_size" + android:layout_height="@dimen/multi_user_avatar_expanded_size" + android:layout_gravity="center" + android:scaleType="centerInside" /> + </com.android.systemui.statusbar.phone.MultiUserSwitch> + + <com.android.systemui.statusbar.AlphaOptimizedFrameLayout + android:id="@+id/settings_button_container" + android:layout_width="@dimen/qs_footer_action_button_size" + android:layout_height="@dimen/qs_footer_action_button_size" + android:layout_marginEnd="@dimen/qs_tile_margin_horizontal" + android:background="@drawable/qs_footer_action_circle" + android:clipChildren="false" + android:clipToPadding="false"> + + <com.android.systemui.statusbar.phone.SettingsButton + android:id="@+id/settings_button" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_footer_action_button_size" + android:layout_gravity="center" + android:background="@android:color/transparent" + android:contentDescription="@string/accessibility_quick_settings_settings" + android:padding="@dimen/qs_footer_icon_padding" + android:scaleType="centerInside" + android:src="@drawable/ic_settings" + android:tint="?android:attr/textColorPrimary" /> + + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:id="@+id/tuner_icon" + android:layout_width="8dp" + android:layout_height="8dp" + android:layout_gravity="center_horizontal|bottom" + android:layout_marginBottom="@dimen/qs_footer_icon_padding" + android:src="@drawable/tuner" + android:tint="?android:attr/textColorTertiary" + android:visibility="invisible" /> + + </com.android.systemui.statusbar.AlphaOptimizedFrameLayout> + + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:id="@+id/pm_lite" + android:layout_width="@dimen/qs_footer_action_button_size" + android:layout_height="@dimen/qs_footer_action_button_size" + android:background="@drawable/qs_footer_action_circle_color" + android:clickable="true" + android:clipToPadding="false" + android:focusable="true" + android:padding="@dimen/qs_footer_icon_padding" + android:src="@*android:drawable/ic_lock_power_off" + android:contentDescription="@string/accessibility_quick_settings_power_menu" + android:tint="?androidprv:attr/textColorOnAccent" /> + + </LinearLayout> +</com.android.systemui.qs.FooterActionsView>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml new file mode 100644 index 000000000000..f54c30f2e794 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetTop="@dimen/qs_footer_action_inset" + android:insetBottom="@dimen/qs_footer_action_inset" + android:insetLeft="@dimen/qs_footer_action_inset" + android:insetRight="@dimen/qs_footer_action_inset"> + <ripple + android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="oval"> + <solid android:color="@android:color/white"/> + </shape> + </item> + <item> + <shape android:shape="oval"> + <solid android:color="?attr/offStateColor"/> + </shape> + </item> + + </ripple> +</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml new file mode 100644 index 000000000000..1a323bccfa65 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetTop="@dimen/qs_footer_action_inset" + android:insetBottom="@dimen/qs_footer_action_inset" + android:insetLeft="@dimen/qs_footer_action_inset" + android:insetRight="@dimen/qs_footer_action_inset"> + <ripple + android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="oval"> + <solid android:color="@android:color/white"/> + </shape> + </item> + <item> + <shape android:shape="oval"> + <solid android:color="?android:attr/colorAccent"/> + </shape> + </item> + + </ripple> +</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/overlay_action_chip.xml b/packages/SystemUI/res/layout/overlay_action_chip.xml index 6d2d93124234..e0c20ff4269c 100644 --- a/packages/SystemUI/res/layout/overlay_action_chip.xml +++ b/packages/SystemUI/res/layout/overlay_action_chip.xml @@ -17,6 +17,7 @@ <com.android.systemui.screenshot.OverlayActionChip xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/overlay_action_chip" + android:theme="@style/FloatingOverlay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/overlay_action_chip_margin_start" diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index 5cd9e9485fda..b6e34998a7cd 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -19,7 +19,7 @@ <com.android.systemui.qs.QSFooterView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/qs_footer" android:layout_width="match_parent" - android:layout_height="@dimen/qs_footer_height" + android:layout_height="wrap_content" android:layout_marginStart="@dimen/qs_footer_margin" android:layout_marginEnd="@dimen/qs_footer_margin" android:layout_marginBottom="@dimen/qs_footers_margin_bottom" @@ -36,7 +36,7 @@ <LinearLayout android:layout_width="match_parent" - android:layout_height="48dp" + android:layout_height="@dimen/qs_footer_height" android:layout_gravity="center_vertical"> <TextView @@ -80,8 +80,13 @@ </LinearLayout> - <include layout="@layout/footer_actions" - android:id="@+id/qs_footer_actions"/> + <ViewStub + android:id="@+id/footer_stub" + android:inflatedId="@+id/qs_footer_actions" + android:layout="@layout/footer_actions" + android:layout_height="@dimen/qs_footer_height" + android:layout_width="match_parent" + /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index f5c6036a5a86..22abd0c2f034 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -51,6 +51,15 @@ android:id="@+id/qs_detail" layout="@layout/qs_detail" /> + <ViewStub + android:id="@+id/container_stub" + android:inflatedId="@+id/qs_footer_actions" + android:layout="@layout/new_footer_actions" + android:layout_height="@dimen/qs_footer_height" + android:layout_width="match_parent" + android:layout_gravity="bottom" + /> + <include android:id="@+id/qs_customize" layout="@layout/qs_customize_panel" diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml index 10a2f4c625c6..2c29f071dcbc 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml @@ -57,16 +57,6 @@ android:focusable="true" android:paddingBottom="24dp" android:importantForAccessibility="yes"> - - <include - layout="@layout/footer_actions" - android:id="@+id/qqs_footer_actions" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/qqs_layout_margin_top" - android:layout_marginStart="@dimen/qs_footer_margin" - android:layout_marginEnd="@dimen/qs_footer_margin" - /> </com.android.systemui.qs.QuickQSPanel> </RelativeLayout> diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml index 219fd43c0e83..ae557c4e72fc 100644 --- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> + <!-- * Copyright (c) 2022, The Android Open Source Project * @@ -16,6 +17,7 @@ */ --> <resources> + <dimen name="controls_padding_horizontal">205dp</dimen> <dimen name="split_shade_notifications_scrim_margin_bottom">16dp</dimen> <dimen name="notification_panel_margin_bottom">56dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml index 1564ee8c5604..95df59442978 100644 --- a/packages/SystemUI/res/values-sw720dp/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp/dimens.xml @@ -16,8 +16,9 @@ */ --> <resources> - <!-- gap on either side of status bar notification icons --> <dimen name="status_bar_icon_padding">1dp</dimen> + + <dimen name="controls_padding_horizontal">75dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-w500dp/dimens.xml b/packages/SystemUI/res/values-w500dp/dimens.xml deleted file mode 100644 index 5ce5ceee6dc9..000000000000 --- a/packages/SystemUI/res/values-w500dp/dimens.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<resources> - <dimen name="controls_padding_horizontal">75dp</dimen> -</resources> diff --git a/packages/SystemUI/res/values-w850dp/dimens.xml b/packages/SystemUI/res/values-w850dp/dimens.xml deleted file mode 100644 index bb6ba8fb07b6..000000000000 --- a/packages/SystemUI/res/values-w850dp/dimens.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<resources> - <dimen name="controls_padding_horizontal">205dp</dimen> -</resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 74bb9e45a6f2..dba7290dba09 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -327,7 +327,7 @@ <!-- The height of the quick settings footer that holds the user switcher, settings icon, etc. --> - <dimen name="qs_footer_height">96dp</dimen> + <dimen name="qs_footer_height">48dp</dimen> <!-- The size of each of the icon buttons in the QS footer --> <dimen name="qs_footer_action_button_size">48dp</dimen> @@ -491,7 +491,7 @@ <dimen name="qs_tile_text_size">14sp</dimen> <dimen name="qs_panel_padding">16dp</dimen> <dimen name="qs_dual_tile_padding_horizontal">6dp</dimen> - <dimen name="qs_panel_padding_bottom">0dp</dimen> + <dimen name="qs_panel_padding_bottom">@dimen/qs_footer_height</dimen> <dimen name="qs_panel_padding_top">48dp</dimen> <dimen name="qs_detail_header_padding">0dp</dimen> <dimen name="qs_detail_image_width">56dp</dimen> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl deleted file mode 100644 index 861a4ed8eaf9..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.mediattt; - -parcelable DeviceInfo; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt deleted file mode 100644 index d41aaf30a12f..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.mediattt - -import android.os.Parcel -import android.os.Parcelable - -/** - * Represents a device that can send or receive media. Includes any device information necessary for - * SysUI to display an informative chip to the user. - */ -class DeviceInfo(val name: String) : Parcelable { - constructor(parcel: Parcel) : this(parcel.readString()) - - override fun writeToParcel(dest: Parcel?, flags: Int) { - dest?.writeString(name) - } - - override fun describeContents() = 0 - - override fun toString() = "name: $name" - - companion object CREATOR : Parcelable.Creator<DeviceInfo> { - override fun createFromParcel(parcel: Parcel) = DeviceInfo(parcel) - override fun newArray(size: Int) = arrayOfNulls<DeviceInfo?>(size) - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl deleted file mode 100644 index eb1c9d058e20..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.mediattt; - -import android.media.MediaRoute2Info; -import com.android.systemui.shared.mediattt.DeviceInfo; -import com.android.systemui.shared.mediattt.IUndoTransferCallback; - -/** - * An interface that can be invoked to trigger media transfer events on System UI. - * - * This interface is for the *sender* device, which is the device currently playing media. This - * sender device can transfer the media to a different device, called the receiver. - * - * System UI will implement this interface and other services will invoke it. - */ -interface IDeviceSenderService { - /** - * Invoke to notify System UI that this device (the sender) is close to a receiver device, so - * the user can potentially *start* a cast to the receiver device if the user moves their device - * a bit closer. - * - * Important notes: - * - When this callback triggers, the device is close enough to inform the user that - * transferring is an option, but the device is *not* close enough to actually initiate a - * transfer yet. - * - This callback is for *starting* a cast. It should be used when this device is currently - * playing media locally and the media should be transferred to be played on the receiver - * device instead. - */ - oneway void closeToReceiverToStartCast( - in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); - - /** - * Invoke to notify System UI that this device (the sender) is close to a receiver device, so - * the user can potentially *end* a cast on the receiver device if the user moves this device a - * bit closer. - * - * Important notes: - * - When this callback triggers, the device is close enough to inform the user that - * transferring is an option, but the device is *not* close enough to actually initiate a - * transfer yet. - * - This callback is for *ending* a cast. It should be used when media is currently being - * played on the receiver device and the media should be transferred to play locally - * instead. - */ - oneway void closeToReceiverToEndCast( - in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); - - /** - * Invoke to notify System UI that a media transfer from this device (the sender) to a receiver - * device has been started. - * - * Important notes: - * - This callback is for *starting* a cast. It should be used when this device is currently - * playing media locally and the media has started being transferred to the receiver device - * instead. - */ - oneway void transferToReceiverTriggered( - in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); - - /** - * Invoke to notify System UI that a media transfer from the receiver and back to this device - * (the sender) has been started. - * - * Important notes: - * - This callback is for *ending* a cast. It should be used when media is currently being - * played on the receiver device and the media has started being transferred to play locally - * instead. - */ - oneway void transferToThisDeviceTriggered( - in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); - - /** - * Invoke to notify System UI that a media transfer from this device (the sender) to a receiver - * device has finished successfully. - * - * Important notes: - * - This callback is for *starting* a cast. It should be used when this device had previously - * been playing media locally and the media has successfully been transferred to the - * receiver device instead. - * - * @param undoCallback will be invoked if the user chooses to undo this transfer. - */ - oneway void transferToReceiverSucceeded( - in MediaRoute2Info mediaInfo, - in DeviceInfo otherDeviceInfo, - in IUndoTransferCallback undoCallback); - - /** - * Invoke to notify System UI that a media transfer from the receiver and back to this device - * (the sender) has finished successfully. - * - * Important notes: - * - This callback is for *ending* a cast. It should be used when media was previously being - * played on the receiver device and has been successfully transferred to play locally on - * this device instead. - * - * @param undoCallback will be invoked if the user chooses to undo this transfer. - */ - oneway void transferToThisDeviceSucceeded( - in MediaRoute2Info mediaInfo, - in DeviceInfo otherDeviceInfo, - in IUndoTransferCallback undoCallback); - - /** - * Invoke to notify System UI that the attempted transfer has failed. - * - * This callback will be used for both the transfer that should've *started* playing the media - * on the receiver and the transfer that should've *ended* the playing on the receiver. - */ - oneway void transferFailed(in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); - - /** - * Invoke to notify System UI that this device is no longer close to the receiver device. - */ - oneway void noLongerCloseToReceiver( - in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); -} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index c894b7023d75..357a68fc6502 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -107,6 +107,8 @@ public class Flags { public static final ResourceBooleanFlag QS_USER_DETAIL_SHORTCUT = new ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut); + public static final BooleanFlag NEW_FOOTER = new BooleanFlag(504, false); + /***************************************/ // 600- status bar public static final BooleanFlag COMBINED_STATUS_BAR_SIGNAL_ICONS = diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java index d1a103e3a8fa..de67ba8a6ded 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java @@ -40,6 +40,7 @@ public class GlobalActionsPopupMenu extends ListPopupWindow { private boolean mIsDropDownMode; private int mMenuVerticalPadding = 0; private int mGlobalActionsSidePadding = 0; + private int mMaximumWidthThresholdDp = 800; private ListAdapter mAdapter; private AdapterView.OnItemLongClickListener mOnItemLongClickListener; @@ -92,6 +93,8 @@ public class GlobalActionsPopupMenu extends ListPopupWindow { // width should be between [.5, .9] of screen int parentWidth = res.getSystem().getDisplayMetrics().widthPixels; + float parentDensity = res.getSystem().getDisplayMetrics().density; + float parentWidthDp = parentWidth / parentDensity; int widthSpec = MeasureSpec.makeMeasureSpec( (int) (parentWidth * 0.9), MeasureSpec.AT_MOST); int maxWidth = 0; @@ -101,9 +104,12 @@ public class GlobalActionsPopupMenu extends ListPopupWindow { int w = child.getMeasuredWidth(); maxWidth = Math.max(w, maxWidth); } - int width = Math.max(maxWidth, (int) (parentWidth * 0.5)); - listView.setPadding(0, mMenuVerticalPadding, 0, mMenuVerticalPadding); + int width = maxWidth; + if (parentWidthDp < mMaximumWidthThresholdDp) { + width = Math.max(maxWidth, (int) (parentWidth * 0.5)); + } + listView.setPadding(0, mMenuVerticalPadding, 0, mMenuVerticalPadding); setWidth(width); if (getAnchorView().getLayoutDirection() == LayoutDirection.LTR) { setHorizontalOffset(getAnchorView().getWidth() - mGlobalActionsSidePadding - width); diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt index b15807c0475c..6d589aac2079 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt @@ -176,14 +176,9 @@ class LogBuffer @JvmOverloads constructor( buffer.removeFirst() } buffer.add(message as LogMessageImpl) - if (systrace) { - val messageStr = message.printer(message) - Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", "$name - $messageStr") - } - if (logcatEchoTracker.isBufferLoggable(name, message.level) || - logcatEchoTracker.isTagLoggable(message.tag, message.level)) { - echo(message) - } + val includeInLogcat = logcatEchoTracker.isBufferLoggable(name, message.level) || + logcatEchoTracker.isTagLoggable(message.tag, message.level) + echo(message, toLogcat = includeInLogcat, toSystrace = systrace) } /** Converts the entire buffer to a newline-delimited string */ @@ -232,8 +227,24 @@ class LogBuffer @JvmOverloads constructor( pw.println(message.printer(message)) } - private fun echo(message: LogMessage) { - val strMessage = message.printer(message) + private fun echo(message: LogMessage, toLogcat: Boolean, toSystrace: Boolean) { + if (toLogcat || toSystrace) { + val strMessage = message.printer(message) + if (toSystrace) { + echoToSystrace(message, strMessage) + } + if (toLogcat) { + echoToLogcat(message, strMessage) + } + } + } + + private fun echoToSystrace(message: LogMessage, strMessage: String) { + Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", + "$name - ${message.level.shortString} ${message.tag}: $strMessage") + } + + private fun echoToLogcat(message: LogMessage, strMessage: String) { when (message.level) { LogLevel.VERBOSE -> Log.v(message.tag, strMessage) LogLevel.DEBUG -> Log.d(message.tag, strMessage) diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index 29938a05a327..86845091d99c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -31,7 +31,7 @@ import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper; import com.android.systemui.media.taptotransfer.MediaTttFlags; import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver; import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender; -import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.commandline.CommandRegistry; import java.util.Optional; @@ -100,11 +100,12 @@ public interface MediaModule { static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender( MediaTttFlags mediaTttFlags, Context context, - WindowManager windowManager) { + WindowManager windowManager, + CommandQueue commandQueue) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } - return Optional.of(new MediaTttChipControllerSender(context, windowManager)); + return Optional.of(new MediaTttChipControllerSender(context, windowManager, commandQueue)); } /** */ @@ -138,12 +139,6 @@ public interface MediaModule { mediaTttChipControllerReceiver)); } - /** Inject into MediaTttSenderService. */ - @Binds - @IntoMap - @ClassKey(MediaTttSenderService.class) - Service bindMediaTttSenderService(MediaTttSenderService service); - /** Inject into NearbyMediaDevicesService. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index 37208515120a..bbcbfba5bbe7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -16,21 +16,16 @@ package com.android.systemui.media.taptotransfer -import android.content.ComponentName +import android.app.StatusBarManager import android.content.Context -import android.content.Intent -import android.content.ServiceConnection import android.graphics.Color import android.graphics.drawable.Icon import android.media.MediaRoute2Info -import android.os.IBinder -import android.util.Log import androidx.annotation.VisibleForTesting import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver -import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService import com.android.systemui.media.taptotransfer.sender.MoveCloserToEndCast import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast import com.android.systemui.media.taptotransfer.sender.TransferFailed @@ -38,9 +33,6 @@ import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTrigger import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceSucceeded import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceTriggered import com.android.systemui.media.taptotransfer.sender.TransferToReceiverSucceeded -import com.android.systemui.shared.mediattt.DeviceInfo -import com.android.systemui.shared.mediattt.IDeviceSenderService -import com.android.systemui.shared.mediattt.IUndoTransferCallback import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import java.io.PrintWriter @@ -56,10 +48,7 @@ class MediaTttCommandLineHelper @Inject constructor( private val context: Context, private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver, ) { - private var senderService: IDeviceSenderService? = null - private val senderServiceConnection = SenderServiceConnection() - - private val appIconDrawable = + private val appIconDrawable = Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also { it.setTint(Color.YELLOW) } @@ -75,115 +64,24 @@ class MediaTttCommandLineHelper @Inject constructor( /** All commands for the sender device. */ inner class SenderCommand : Command { override fun execute(pw: PrintWriter, args: List<String>) { - val otherDeviceName = args[0] - val mediaInfo = MediaRoute2Info.Builder("id", "Test Name") - .addFeature("feature") - .build() - val otherDeviceInfo = DeviceInfo(otherDeviceName) - - when (args[1]) { - MOVE_CLOSER_TO_START_CAST_COMMAND_NAME -> { - runOnService { senderService -> - senderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo) - } - } - MOVE_CLOSER_TO_END_CAST_COMMAND_NAME -> { - runOnService { senderService -> - senderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo) - } - } - TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME -> { - runOnService { senderService -> - senderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo) - } - } - TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME -> { - runOnService { senderService -> - senderService.transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo) - } - } - TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME -> { - val undoCallback = object : IUndoTransferCallback.Stub() { - override fun onUndoTriggered() { - Log.i(TAG, "Undo transfer to receiver callback triggered") - // The external services that implement this callback would kick off a - // transfer back to this device, so mimic that here. - runOnService { senderService -> - senderService - .transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo) - } - } - } - runOnService { senderService -> - senderService - .transferToReceiverSucceeded(mediaInfo, otherDeviceInfo, undoCallback) - } - } - TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME -> { - val undoCallback = object : IUndoTransferCallback.Stub() { - override fun onUndoTriggered() { - Log.i(TAG, "Undo transfer to this device callback triggered") - // The external services that implement this callback would kick off a - // transfer back to the receiver, so mimic that here. - runOnService { senderService -> - senderService - .transferToReceiverTriggered(mediaInfo, otherDeviceInfo) - } - } - } - runOnService { senderService -> - senderService - .transferToThisDeviceSucceeded(mediaInfo, otherDeviceInfo, undoCallback) - } - } - TRANSFER_FAILED_COMMAND_NAME -> { - runOnService { senderService -> - senderService.transferFailed(mediaInfo, otherDeviceInfo) - } - } - NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME -> { - runOnService { senderService -> - senderService.noLongerCloseToReceiver(mediaInfo, otherDeviceInfo) - context.unbindService(senderServiceConnection) - } - } - else -> { - pw.println("Sender command must be one of " + - "$MOVE_CLOSER_TO_START_CAST_COMMAND_NAME, " + - "$MOVE_CLOSER_TO_END_CAST_COMMAND_NAME, " + - "$TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME, " + - "$TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME, " + - "$TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME, " + - "$TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME, " + - "$TRANSFER_FAILED_COMMAND_NAME, " + - NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME - ) - } - } + val routeInfo = MediaRoute2Info.Builder("id", args[0]) + .addFeature("feature") + .build() + + val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE) + as StatusBarManager + statusBarManager.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + routeInfo, + /* undoExecutor= */ null, + /* undoCallback= */ null + ) + // TODO(b/216318437): Migrate the rest of the callbacks to StatusBarManager. } override fun help(pw: PrintWriter) { pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND <deviceName> <chipStatus>") } - - private fun runOnService(command: SenderServiceCommand) { - val currentService = senderService - if (currentService != null) { - command.run(currentService) - } else { - bindService(command) - } - } - - private fun bindService(command: SenderServiceCommand) { - senderServiceConnection.pendingCommand = command - val binding = context.bindService( - Intent(context, MediaTttSenderService::class.java), - senderServiceConnection, - Context.BIND_AUTO_CREATE - ) - Log.i(TAG, "Starting service binding? $binding") - } } /** A command to DISPLAY the media ttt chip on the RECEIVER device. */ @@ -207,29 +105,6 @@ class MediaTttCommandLineHelper @Inject constructor( pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_RECEIVER_TAG") } } - - /** A service connection for [IDeviceSenderService]. */ - private inner class SenderServiceConnection : ServiceConnection { - // A command that should be run when the service gets connected. - var pendingCommand: SenderServiceCommand? = null - - override fun onServiceConnected(className: ComponentName, service: IBinder) { - val newCallback = IDeviceSenderService.Stub.asInterface(service) - senderService = newCallback - pendingCommand?.run(newCallback) - pendingCommand = null - } - - override fun onServiceDisconnected(className: ComponentName) { - senderService = null - } - } - - /** An interface defining a command that should be run on the sender service. */ - private fun interface SenderServiceCommand { - /** Runs the command on the provided [senderService]. */ - fun run(senderService: IDeviceSenderService) - } } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index c656df2e0a35..118a04ccdcd0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -19,9 +19,9 @@ package com.android.systemui.media.taptotransfer.sender import android.content.Context import android.graphics.drawable.Drawable import android.view.View +import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R import com.android.systemui.media.taptotransfer.common.MediaTttChipState -import com.android.systemui.shared.mediattt.IUndoTransferCallback /** * A class that stores all the information necessary to display the media tap-to-transfer chip on @@ -130,7 +130,7 @@ class TransferToReceiverSucceeded( appIconDrawable: Drawable, appIconContentDescription: String, private val otherDeviceName: String, - val undoCallback: IUndoTransferCallback? = null + val undoCallback: IUndoMediaTransferCallback? = null ) : ChipStateSender(appIconDrawable, appIconContentDescription) { override fun getChipTextString(context: Context): String { return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName) @@ -169,7 +169,7 @@ class TransferToThisDeviceSucceeded( appIconDrawable: Drawable, appIconContentDescription: String, private val otherDeviceName: String, - val undoCallback: IUndoTransferCallback? = null + val undoCallback: IUndoMediaTransferCallback? = null ) : ChipStateSender(appIconDrawable, appIconContentDescription) { override fun getChipTextString(context: Context): String { return context.getString(R.string.media_transfer_playing_this_device) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 453e3d627bc8..c510e358e4a5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -16,14 +16,20 @@ package com.android.systemui.media.taptotransfer.sender +import android.app.StatusBarManager import android.content.Context +import android.graphics.Color +import android.graphics.drawable.Icon +import android.media.MediaRoute2Info import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.widget.TextView +import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon +import com.android.systemui.statusbar.CommandQueue import javax.inject.Inject /** @@ -34,9 +40,36 @@ import javax.inject.Inject class MediaTttChipControllerSender @Inject constructor( context: Context, windowManager: WindowManager, + private val commandQueue: CommandQueue ) : MediaTttChipControllerCommon<ChipStateSender>( context, windowManager, R.layout.media_ttt_chip ) { + // TODO(b/216141276): Use app icon from media route info instead of this fake one. + private val fakeAppIconDrawable = + Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also { + it.setTint(Color.YELLOW) + } + + private val commandQueueCallback = object : CommandQueue.Callbacks { + override fun updateMediaTapToTransferSenderDisplay( + @StatusBarManager.MediaTransferSenderState displayState: Int, + routeInfo: MediaRoute2Info, + undoCallback: IUndoMediaTransferCallback? + ) { + // TODO(b/216318437): Trigger displayChip with the right state based on displayState. + displayChip( + MoveCloserToStartCast( + // TODO(b/217418566): This app icon content description is incorrect -- + // routeInfo.name is the name of the device, not the name of the app. + fakeAppIconDrawable, routeInfo.name.toString(), routeInfo.name.toString() + ) + ) + } + } + + init { + commandQueue.addCallback(commandQueueCallback) + } /** Displays the chip view for the given state. */ override fun updateChipView(chipState: ChipStateSender, currentChipView: ViewGroup) { diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt deleted file mode 100644 index 717752e536b0..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media.taptotransfer.sender - -import android.app.Service -import android.content.Context -import android.content.Intent -import android.graphics.Color -import android.graphics.drawable.Icon -import android.media.MediaRoute2Info -import android.os.IBinder -import com.android.systemui.R -import com.android.systemui.shared.mediattt.DeviceInfo -import com.android.systemui.shared.mediattt.IUndoTransferCallback -import com.android.systemui.shared.mediattt.IDeviceSenderService -import javax.inject.Inject - -/** - * Service that allows external handlers to trigger the media chip on the sender device. - */ -class MediaTttSenderService @Inject constructor( - context: Context, - val controller: MediaTttChipControllerSender -) : Service() { - - // TODO(b/203800643): Add logging when callbacks trigger. - private val binder: IBinder = object : IDeviceSenderService.Stub() { - override fun closeToReceiverToStartCast( - mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo - ) { - this@MediaTttSenderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo) - } - - override fun closeToReceiverToEndCast( - mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo - ) { - this@MediaTttSenderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo) - } - - override fun transferFailed( - mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo - ) { - this@MediaTttSenderService.transferFailed(mediaInfo) - } - - override fun transferToReceiverTriggered( - mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo - ) { - this@MediaTttSenderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo) - } - - override fun transferToThisDeviceTriggered( - mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo - ) { - this@MediaTttSenderService.transferToThisDeviceTriggered(mediaInfo) - } - - override fun transferToReceiverSucceeded( - mediaInfo: MediaRoute2Info, - otherDeviceInfo: DeviceInfo, - undoCallback: IUndoTransferCallback - ) { - this@MediaTttSenderService.transferToReceiverSucceeded( - mediaInfo, otherDeviceInfo, undoCallback - ) - } - - override fun transferToThisDeviceSucceeded( - mediaInfo: MediaRoute2Info, - otherDeviceInfo: DeviceInfo, - undoCallback: IUndoTransferCallback - ) { - this@MediaTttSenderService.transferToThisDeviceSucceeded( - mediaInfo, otherDeviceInfo, undoCallback - ) - } - - override fun noLongerCloseToReceiver( - mediaInfo: MediaRoute2Info, - otherDeviceInfo: DeviceInfo - ) { - this@MediaTttSenderService.noLongerCloseToReceiver() - } - } - - // TODO(b/203800643): Use the app icon from the media info instead of a fake one. - private val fakeAppIconDrawable = - Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also { - it.setTint(Color.YELLOW) - } - - override fun onBind(intent: Intent?): IBinder = binder - - private fun closeToReceiverToStartCast( - mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo - ) { - val chipState = MoveCloserToStartCast( - appIconDrawable = fakeAppIconDrawable, - appIconContentDescription = mediaInfo.name.toString(), - otherDeviceName = otherDeviceInfo.name - ) - controller.displayChip(chipState) - } - - private fun closeToReceiverToEndCast(mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo) { - val chipState = MoveCloserToEndCast( - appIconDrawable = fakeAppIconDrawable, - appIconContentDescription = mediaInfo.name.toString(), - otherDeviceName = otherDeviceInfo.name - ) - controller.displayChip(chipState) - } - - private fun transferFailed(mediaInfo: MediaRoute2Info) { - val chipState = TransferFailed( - appIconDrawable = fakeAppIconDrawable, - appIconContentDescription = mediaInfo.name.toString() - ) - controller.displayChip(chipState) - } - - private fun transferToReceiverTriggered( - mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo - ) { - val chipState = TransferToReceiverTriggered( - appIconDrawable = fakeAppIconDrawable, - appIconContentDescription = mediaInfo.name.toString(), - otherDeviceName = otherDeviceInfo.name - ) - controller.displayChip(chipState) - } - - private fun transferToThisDeviceTriggered(mediaInfo: MediaRoute2Info) { - val chipState = TransferToThisDeviceTriggered( - appIconDrawable = fakeAppIconDrawable, - appIconContentDescription = mediaInfo.name.toString() - ) - controller.displayChip(chipState) - } - - private fun transferToReceiverSucceeded( - mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback - ) { - val chipState = TransferToReceiverSucceeded( - appIconDrawable = fakeAppIconDrawable, - appIconContentDescription = mediaInfo.name.toString(), - otherDeviceName = otherDeviceInfo.name, - undoCallback = undoCallback - ) - controller.displayChip(chipState) - } - - private fun transferToThisDeviceSucceeded( - mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback - ) { - val chipState = TransferToThisDeviceSucceeded( - appIconDrawable = fakeAppIconDrawable, - appIconContentDescription = mediaInfo.name.toString(), - otherDeviceName = otherDeviceInfo.name, - undoCallback = undoCallback - ) - controller.displayChip(chipState) - } - - private fun noLongerCloseToReceiver() { - controller.removeChip() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index 7ac9205c7922..4aedbc983d9c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -29,14 +29,16 @@ import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger import com.android.internal.logging.nano.MetricsProto import com.android.keyguard.KeyguardUpdateMonitor +import com.android.settingslib.Utils import com.android.systemui.R import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.globalactions.GlobalActionsDialogLite import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager -import com.android.systemui.qs.FooterActionsController.ExpansionState.COLLAPSED -import com.android.systemui.qs.FooterActionsController.ExpansionState.EXPANDED import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED +import com.android.systemui.qs.dagger.QSScope import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.MultiUserSwitchController import com.android.systemui.statusbar.phone.SettingsButton @@ -54,13 +56,14 @@ import javax.inject.Named * Main difference between QS and QQS behaviour is condition when buttons should be visible, * determined by [buttonsVisibleState] */ +@QSScope class FooterActionsController @Inject constructor( view: FooterActionsView, + multiUserSwitchControllerFactory: MultiUserSwitchController.Factory, private val activityStarter: ActivityStarter, private val userManager: UserManager, private val userTracker: UserTracker, private val userInfoController: UserInfoController, - private val multiUserSwitchController: MultiUserSwitchController, private val deviceProvisionedController: DeviceProvisionedController, private val falsingManager: FalsingManager, private val metricsLogger: MetricsLogger, @@ -68,20 +71,34 @@ class FooterActionsController @Inject constructor( private val globalActionsDialog: GlobalActionsDialogLite, private val uiEventLogger: UiEventLogger, @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean, - private val buttonsVisibleState: ExpansionState, private val globalSetting: GlobalSettings, - private val handler: Handler + private val handler: Handler, + private val featureFlags: FeatureFlags ) : ViewController<FooterActionsView>(view) { - enum class ExpansionState { COLLAPSED, EXPANDED } - + private var lastExpansion = -1f private var listening: Boolean = false - var expanded = false + private val alphaAnimator = TouchAnimator.Builder() + .addFloat(mView, "alpha", 0f, 1f) + .setStartDelay(0.9f) + .build() + + var visible = true + set(value) { + field = value + updateVisibility() + } + + init { + view.elevation = resources.displayMetrics.density * 4f + view.setBackgroundColor(Utils.getColorAttrDefaultColor(context, R.attr.underSurfaceColor)) + } private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button) private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container) private val powerMenuLite: View = view.findViewById(R.id.pm_lite) + private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view) private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ -> val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) @@ -99,9 +116,8 @@ class FooterActionsController @Inject constructor( } private val onClickListener = View.OnClickListener { v -> - // Don't do anything until views are unhidden. Don't do anything if the tap looks - // suspicious. - if (!buttonsVisible() || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + // Don't do anything if the tap looks suspicious. + if (!visible || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return@OnClickListener } if (v === settingsButton) { @@ -110,9 +126,7 @@ class FooterActionsController @Inject constructor( activityStarter.postQSRunnableDismissingKeyguard {} return@OnClickListener } - metricsLogger.action( - if (expanded) MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH - else MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH) + metricsLogger.action(MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH) if (settingsButton.isTunerClick) { activityStarter.postQSRunnableDismissingKeyguard { if (isTunerEnabled()) { @@ -135,24 +149,14 @@ class FooterActionsController @Inject constructor( } } - private fun buttonsVisible(): Boolean { - return when (buttonsVisibleState) { - EXPANDED -> expanded - COLLAPSED -> !expanded - } - } - override fun onInit() { multiUserSwitchController.init() } - fun hideFooter() { - mView.visibility = View.GONE - } - - fun showFooter() { - mView.visibility = View.VISIBLE - updateView() + private fun updateVisibility() { + val previousVisibility = mView.visibility + mView.visibility = if (visible) View.VISIBLE else View.INVISIBLE + if (previousVisibility != mView.visibility) updateView() } private fun startSettingsActivity() { @@ -204,24 +208,23 @@ class FooterActionsController @Inject constructor( } fun setExpansion(headerExpansionFraction: Float) { - mView.setExpansion(headerExpansionFraction) - } - - fun updateAnimator(width: Int, numTiles: Int) { - mView.updateAnimator(width, numTiles) - } - - fun setKeyguardShowing() { - mView.setKeyguardShowing() - } - - fun refreshVisibility(shouldBeVisible: Boolean) { - if (shouldBeVisible) { - showFooter() + if (featureFlags.isEnabled(Flags.NEW_FOOTER)) { + if (headerExpansionFraction != lastExpansion) { + if (headerExpansionFraction >= 1f) { + mView.animate().alpha(1f).setDuration(500L).start() + } else if (lastExpansion >= 1f && headerExpansionFraction < 1f) { + mView.animate().alpha(0f).setDuration(250L).start() + } + lastExpansion = headerExpansionFraction + } } else { - hideFooter() + alphaAnimator.setPosition(headerExpansionFraction) } } + fun setKeyguardShowing(showing: Boolean) { + setExpansion(lastExpansion) + } + private fun isTunerEnabled() = tunerService.isTunerEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt deleted file mode 100644 index 7694be51cba6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs - -import android.os.Handler -import android.os.UserManager -import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.globalactions.GlobalActionsDialogLite -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.plugins.FalsingManager -import com.android.systemui.qs.FooterActionsController.ExpansionState -import com.android.systemui.qs.dagger.QSFlagsModule -import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.phone.MultiUserSwitchController -import com.android.systemui.statusbar.policy.DeviceProvisionedController -import com.android.systemui.statusbar.policy.UserInfoController -import com.android.systemui.tuner.TunerService -import com.android.systemui.util.settings.GlobalSettings -import javax.inject.Inject -import javax.inject.Named - -class FooterActionsControllerBuilder @Inject constructor( - private val activityStarter: ActivityStarter, - private val userManager: UserManager, - private val userTracker: UserTracker, - private val userInfoController: UserInfoController, - private val multiUserSwitchControllerFactory: MultiUserSwitchController.Factory, - private val deviceProvisionedController: DeviceProvisionedController, - private val falsingManager: FalsingManager, - private val metricsLogger: MetricsLogger, - private val tunerService: TunerService, - private val globalActionsDialog: GlobalActionsDialogLite, - private val uiEventLogger: UiEventLogger, - @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean, - private val globalSettings: GlobalSettings, - @Main private val handler: Handler -) { - private lateinit var view: FooterActionsView - private lateinit var buttonsVisibleState: ExpansionState - - fun withView(view: FooterActionsView): FooterActionsControllerBuilder { - this.view = view - return this - } - - fun withButtonsVisibleWhen(state: ExpansionState): FooterActionsControllerBuilder { - buttonsVisibleState = state - return this - } - - fun build(): FooterActionsController { - return FooterActionsController(view, activityStarter, userManager, - userTracker, userInfoController, multiUserSwitchControllerFactory.create(view), - deviceProvisionedController, falsingManager, metricsLogger, tunerService, - globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState, - globalSettings, handler) - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt index e6fa2ae8dad1..18e0cfa910ad 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt @@ -44,8 +44,6 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( private lateinit var multiUserAvatar: ImageView private lateinit var tunerIcon: View - private var settingsCogAnimator: TouchAnimator? = null - private var qsDisabled = false private var expansionAmount = 0f @@ -66,19 +64,6 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES } - fun updateAnimator(width: Int, numTiles: Int) { - val size = (mContext.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size) - - mContext.resources.getDimensionPixelSize(R.dimen.qs_tile_padding)) - val remaining = (width - numTiles * size) / (numTiles - 1) - val defSpace = mContext.resources.getDimensionPixelOffset(R.dimen.default_gear_space) - val translation = if (isLayoutRtl) (remaining - defSpace) else -(remaining - defSpace) - settingsCogAnimator = TouchAnimator.Builder() - .addFloat(settingsButton, "translationX", translation.toFloat(), 0f) - .addFloat(settingsButton, "rotation", -120f, 0f) - .build() - setExpansion(expansionAmount) - } - override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) updateResources() @@ -95,15 +80,6 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( tunerIcon.translationX = if (isLayoutRtl) (-tunerIconTranslation) else tunerIconTranslation } - fun setKeyguardShowing() { - setExpansion(expansionAmount) - } - - fun setExpansion(headerExpansionFraction: Float) { - expansionAmount = headerExpansionFraction - if (settingsCogAnimator != null) settingsCogAnimator!!.setPosition(headerExpansionFraction) - } - fun disable( state2: Int, isTunerEnabled: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index ded6ae0ac2ff..d1b569f7f438 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -14,9 +14,6 @@ package com.android.systemui.qs; -import static com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER; -import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FOOTER; - import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.util.Log; @@ -49,7 +46,6 @@ import java.util.List; import java.util.concurrent.Executor; import javax.inject.Inject; -import javax.inject.Named; /** */ @QSScope @@ -88,8 +84,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private final QSFgsManagerFooter mFgsManagerFooter; private final QSSecurityFooter mSecurityFooter; private final QS mQs; - private final View mQSFooterActions; - private final View mQQSFooterActions; @Nullable private PagedTileLayout mPagedLayout; @@ -154,16 +148,12 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost, QSFgsManagerFooter fgsManagerFooter, QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService, - QSExpansionPathInterpolator qsExpansionPathInterpolator, - @Named(QS_FOOTER) FooterActionsView qsFooterActionsView, - @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView) { + QSExpansionPathInterpolator qsExpansionPathInterpolator) { mQs = qs; mQuickQsPanel = quickPanel; mQsPanelController = qsPanelController; mQuickQSPanelController = quickQSPanelController; mQuickStatusBarHeader = quickStatusBarHeader; - mQQSFooterActions = qqsFooterActionsView; - mQSFooterActions = qsFooterActionsView; mFgsManagerFooter = fgsManagerFooter; mSecurityFooter = securityFooter; mHost = qsTileHost; @@ -476,12 +466,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha .setListener(this) .build(); - if (mQQSFooterActions.getVisibility() != View.GONE) { - // only when qqs footer is present (which means split shade mode) it needs to - // be animated - updateQQSFooterAnimation(); - } - // Fade in the security footer and the divider as we reach the final position Builder builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY); builder.addFloat(mFgsManagerFooter.getView(), "alpha", 0, 1); @@ -627,14 +611,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } } - private void updateQQSFooterAnimation() { - int translationY = getRelativeTranslationY(mQSFooterActions, mQQSFooterActions); - mQQSFooterActionsAnimator = new TouchAnimator.Builder() - .addFloat(mQQSFooterActions, "translationY", 0, translationY) - .build(); - mAnimatedQsViews.add(mQSFooterActions); - } - private int getRelativeTranslationY(View view1, View view2) { int[] qsPosition = new int[2]; int[] qqsPosition = new int[2]; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index e230e1bf8051..7800027ea967 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -19,10 +19,8 @@ package com.android.systemui.qs; import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Path; -import android.graphics.Point; import android.graphics.PointF; import android.util.AttributeSet; import android.view.View; @@ -41,7 +39,6 @@ import java.io.PrintWriter; */ public class QSContainerImpl extends FrameLayout implements Dumpable { - private final Point mSizePoint = new Point(); private int mFancyClippingTop; private int mFancyClippingBottom; private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0}; @@ -78,12 +75,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { } @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mSizePoint.set(0, 0); // Will be retrieved on next measure pass. - } - - @Override public boolean performClick() { // Want to receive clicks so missing QQS tiles doesn't cause collapse, but // don't want to do anything with them. @@ -152,10 +143,10 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { void updateResources(QSPanelController qsPanelController, QuickStatusBarHeaderController quickStatusBarHeaderController) { mQSPanelContainer.setPaddingRelative( - getPaddingStart(), + mQSPanelContainer.getPaddingStart(), Utils.getQsHeaderSystemIconsAreaHeight(mContext), - getPaddingEnd(), - getPaddingBottom() + mQSPanelContainer.getPaddingEnd(), + mQSPanelContainer.getPaddingBottom() ); int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); @@ -241,13 +232,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { } } - private int getDisplayHeight() { - if (mSizePoint.y == 0) { - getDisplay().getRealSize(mSizePoint); - } - return mSizePoint.y; - } - /** * Clip QS bottom using a concave shape. */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java index 0e0681b94c62..aac5672aa3f9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java @@ -42,11 +42,6 @@ public interface QSFooter { */ void setExpansion(float expansion); - /** - * Sets whether or not this footer should set itself to listen for changes in any callbacks - * that it has implemented. - */ - void setListening(boolean listening); /** * Sets whether or not the keyguard is currently being shown. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java index 4622660a6c15..6c0ca49c81a8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java @@ -46,7 +46,6 @@ import com.android.systemui.R; public class QSFooterView extends FrameLayout { private PageIndicator mPageIndicator; private TextView mBuildText; - private View mActionsContainer; private View mEditButton; @Nullable @@ -78,7 +77,6 @@ public class QSFooterView extends FrameLayout { protected void onFinishInflate() { super.onFinishInflate(); mPageIndicator = findViewById(R.id.footer_page_indicator); - mActionsContainer = requireViewById(R.id.qs_footer_actions); mBuildText = findViewById(R.id.build); mEditButton = findViewById(android.R.id.edit); @@ -105,10 +103,6 @@ public class QSFooterView extends FrameLayout { } } - void updateExpansion() { - setExpansion(mExpansionAmount); - } - @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -129,7 +123,6 @@ public class QSFooterView extends FrameLayout { @Nullable private TouchAnimator createFooterAnimator() { TouchAnimator.Builder builder = new TouchAnimator.Builder() - .addFloat(mActionsContainer, "alpha", 0, 1) .addFloat(mPageIndicator, "alpha", 0, 1) .addFloat(mBuildText, "alpha", 0, 1) .addFloat(mEditButton, "alpha", 0, 1) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java index 5327b7e3ba26..bef4f43b9d11 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -16,8 +16,6 @@ package com.android.systemui.qs; -import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FOOTER; - import android.content.ClipData; import android.content.ClipboardManager; import android.text.TextUtils; @@ -33,7 +31,6 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.util.ViewController; import javax.inject.Inject; -import javax.inject.Named; /** * Controller for {@link QSFooterView}. @@ -43,8 +40,6 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private final UserTracker mUserTracker; private final QSPanelController mQsPanelController; - private final QuickQSPanelController mQuickQSPanelController; - private final FooterActionsController mFooterActionsController; private final TextView mBuildText; private final PageIndicator mPageIndicator; private final View mEditButton; @@ -56,14 +51,10 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme UserTracker userTracker, FalsingManager falsingManager, ActivityStarter activityStarter, - QSPanelController qsPanelController, - QuickQSPanelController quickQSPanelController, - @Named(QS_FOOTER) FooterActionsController footerActionsController) { + QSPanelController qsPanelController) { super(view); mUserTracker = userTracker; mQsPanelController = qsPanelController; - mQuickQSPanelController = quickQSPanelController; - mFooterActionsController = footerActionsController; mFalsingManager = falsingManager; mActivityStarter = activityStarter; @@ -73,21 +64,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme } @Override - protected void onInit() { - super.onInit(); - mFooterActionsController.init(); - } - - @Override protected void onViewAttached() { - mView.addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { - mView.updateExpansion(); - mFooterActionsController.updateAnimator(right - left, - mQuickQSPanelController.getNumQuickTiles()); - } - ); - mBuildText.setOnLongClickListener(view -> { CharSequence buildText = mBuildText.getText(); if (!TextUtils.isEmpty(buildText)) { @@ -114,9 +91,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme } @Override - protected void onViewDetached() { - setListening(false); - } + protected void onViewDetached() {} @Override public void setVisibility(int visibility) { @@ -126,25 +101,17 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme @Override public void setExpanded(boolean expanded) { - mFooterActionsController.setExpanded(expanded); mView.setExpanded(expanded); } @Override public void setExpansion(float expansion) { mView.setExpansion(expansion); - mFooterActionsController.setExpansion(expansion); - } - - @Override - public void setListening(boolean listening) { - mFooterActionsController.setListening(listening); } @Override public void setKeyguardShowing(boolean keyguardShowing) { mView.setKeyguardShowing(); - mFooterActionsController.setKeyguardShowing(); } /** */ @@ -156,6 +123,5 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme @Override public void disable(int state1, int state2, boolean animate) { mView.disable(state2); - mFooterActionsController.disable(state2); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 259b786dafb7..50952bd0ec28 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -118,6 +118,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private QSPanelController mQSPanelController; private QuickQSPanelController mQuickQSPanelController; private QSCustomizerController mQSCustomizerController; + private FooterActionsController mQSFooterActionController; @Nullable private ScrollListener mScrollListener; /** @@ -188,9 +189,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this); mQSPanelController = qsFragmentComponent.getQSPanelController(); mQuickQSPanelController = qsFragmentComponent.getQuickQSPanelController(); + mQSFooterActionController = qsFragmentComponent.getQSFooterActionController(); mQSPanelController.init(); mQuickQSPanelController.init(); + mQSFooterActionController.init(); mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view); mQSPanelScrollView.addOnLayoutChangeListener( @@ -380,6 +383,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mContainer.disable(state1, state2, animate); mHeader.disable(state1, state2, animate); mFooter.disable(state1, state2, animate); + mQSFooterActionController.disable(state2); updateQsState(); } @@ -396,10 +400,10 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca : View.INVISIBLE); mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) || (expanded && !mStackScrollerOverscrolling), mQuickQSPanelController); - mFooter.setVisibility(!mQsDisabled && (expanded || !keyguardShowing || mHeaderAnimating - || mShowCollapsedOnKeyguard) - ? View.VISIBLE - : View.INVISIBLE); + boolean footerVisible = !mQsDisabled && (expanded || !keyguardShowing || mHeaderAnimating + || mShowCollapsedOnKeyguard); + mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE); + mQSFooterActionController.setVisible(footerVisible); mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) || (expanded && !mStackScrollerOverscrolling)); mQSPanelController.setVisibility( @@ -465,6 +469,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } mFooter.setKeyguardShowing(keyguardShowing); + mQSFooterActionController.setKeyguardShowing(keyguardShowing); updateQsState(); } @@ -480,14 +485,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca if (DEBUG) Log.d(TAG, "setListening " + listening); mListening = listening; mQSContainerImplController.setListening(listening); - mFooter.setListening(listening); + mQSFooterActionController.setListening(listening); mQSPanelController.setListening(mListening, mQsExpanded); } @Override public void setHeaderListening(boolean listening) { mQSContainerImplController.setListening(listening); - mFooter.setListening(listening); } @Override @@ -561,6 +565,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } } mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion); + mQSFooterActionController.setExpansion(onKeyguardAndExpanded ? 1 : expansion); mQSPanelController.setRevealExpansion(expansion); mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); @@ -711,6 +716,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca boolean customizing = isCustomizing(); mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); + mQSFooterActionController.setVisible(!customizing); mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); // Let the panel know the position changed and it needs to update where notifications // and whatnot are. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index 92690c7d1202..2d2fa1f08452 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -17,7 +17,6 @@ package com.android.systemui.qs; import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL; -import static com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER; import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; import com.android.internal.logging.MetricsLogger; @@ -54,7 +53,6 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> // brightness is visible only in split shade private final QuickQSBrightnessController mBrightnessController; private final BrightnessMirrorHandler mBrightnessMirrorHandler; - private final FooterActionsController mFooterActionsController; @Inject QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost, @@ -63,14 +61,12 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Named(QUICK_QS_PANEL) MediaHost mediaHost, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, DumpManager dumpManager, - QuickQSBrightnessController quickQSBrightnessController, - @Named(QQS_FOOTER) FooterActionsController footerActionsController + QuickQSBrightnessController quickQSBrightnessController ) { super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager); mBrightnessController = quickQSBrightnessController; mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController); - mFooterActionsController = footerActionsController; } @Override @@ -80,8 +76,6 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> mMediaHost.setShowsOnlyActiveMedia(true); mMediaHost.init(MediaHierarchyManager.LOCATION_QQS); mBrightnessController.init(mShouldUseSplitNotificationShade); - mFooterActionsController.init(); - mFooterActionsController.refreshVisibility(mShouldUseSplitNotificationShade); } @Override @@ -102,7 +96,6 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> void setListening(boolean listening) { super.setListening(listening); mBrightnessController.setListening(listening); - mFooterActionsController.setListening(listening); } public boolean isListening() { @@ -123,7 +116,6 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Override protected void onConfigurationChanged() { mBrightnessController.refreshVisibility(mShouldUseSplitNotificationShade); - mFooterActionsController.refreshVisibility(mShouldUseSplitNotificationShade); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java index 63cbc21ddf74..594f4f86a680 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.dagger; +import com.android.systemui.qs.FooterActionsController; import com.android.systemui.qs.QSAnimator; import com.android.systemui.qs.QSContainerImplController; import com.android.systemui.qs.QSFooter; @@ -61,4 +62,7 @@ public interface QSFragmentComponent { /** Construct a {@link QSSquishinessController}. */ QSSquishinessController getQSSquishinessController(); + + /** Construct a {@link FooterActionsController}. */ + FooterActionsController getQSFooterActionController(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java index 1958cafb0fb9..776ee1021db2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java @@ -21,15 +21,15 @@ import static com.android.systemui.util.Utils.useQsMediaPlayer; import android.content.Context; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewStub; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.dagger.qualifiers.RootView; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.qs.QS; import com.android.systemui.privacy.OngoingPrivacyChip; -import com.android.systemui.qs.FooterActionsController; -import com.android.systemui.qs.FooterActionsController.ExpansionState; -import com.android.systemui.qs.FooterActionsControllerBuilder; import com.android.systemui.qs.FooterActionsView; import com.android.systemui.qs.QSContainerImpl; import com.android.systemui.qs.QSFooter; @@ -55,8 +55,6 @@ import dagger.Provides; public interface QSFragmentModule { String QS_FGS_MANAGER_FOOTER_VIEW = "qs_fgs_manager_footer"; String QS_SECURITY_FOOTER_VIEW = "qs_security_footer"; - String QQS_FOOTER = "qqs_footer"; - String QS_FOOTER = "qs_footer"; String QS_USING_MEDIA_PLAYER = "qs_using_media_player"; /** @@ -122,46 +120,26 @@ public interface QSFragmentModule { return view.findViewById(R.id.qs_footer); } - /** */ + /** + * Provides a {@link FooterActionsView}. + * + * This will replace a ViewStub either in {@link QSFooterView} or in {@link QSContainerImpl}. + */ @Provides - @Named(QS_FOOTER) - static FooterActionsView providesQSFooterActionsView(@RootView View view) { + static FooterActionsView providesQSFooterActionsView(@RootView View view, + FeatureFlags featureFlags) { + ViewStub stub; + if (featureFlags.isEnabled(Flags.NEW_FOOTER)) { + stub = view.requireViewById(R.id.container_stub); + } else { + stub = view.requireViewById(R.id.footer_stub); + } + stub.inflate(); return view.findViewById(R.id.qs_footer_actions); } /** */ @Provides - @Named(QQS_FOOTER) - static FooterActionsView providesQQSFooterActionsView(@RootView View view) { - return view.findViewById(R.id.qqs_footer_actions); - } - - /** */ - @Provides - @Named(QQS_FOOTER) - static FooterActionsController providesQQSFooterActionsController( - FooterActionsControllerBuilder footerActionsControllerBuilder, - @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView) { - return footerActionsControllerBuilder - .withView(qqsFooterActionsView) - .withButtonsVisibleWhen(ExpansionState.COLLAPSED) - .build(); - } - - /** */ - @Provides - @Named(QS_FOOTER) - static FooterActionsController providesQSFooterActionsController( - FooterActionsControllerBuilder footerActionsControllerBuilder, - @Named(QS_FOOTER) FooterActionsView qsFooterActionsView) { - return footerActionsControllerBuilder - .withView(qsFooterActionsView) - .withButtonsVisibleWhen(ExpansionState.EXPANDED) - .build(); - } - - /** */ - @Provides @QSScope static QSFooter providesQSFooter(QSFooterViewController qsFooterViewController) { qsFooterViewController.init(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 83d8d19a6d4d..30456a8c4cc7 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -344,7 +344,7 @@ public class ScreenshotController { }; mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter( ClipboardOverlayController.COPY_OVERLAY_ACTION), - ClipboardOverlayController.SELF_PERMISSION, null); + ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED); } void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 9d43d303b834..2f5eaa621283 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -43,6 +43,7 @@ import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManager; import android.hardware.fingerprint.IUdfpsHbmListener; import android.inputmethodservice.InputMethodService.BackDispositionMode; +import android.media.MediaRoute2Info; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -63,6 +64,7 @@ import androidx.annotation.NonNull; import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.IStatusBar; +import com.android.internal.statusbar.IUndoMediaTransferCallback; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.GcUtils; import com.android.internal.view.AppearanceRegion; @@ -156,6 +158,8 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_TILE_SERVICE_REQUEST_ADD = 61 << MSG_SHIFT; private static final int MSG_TILE_SERVICE_REQUEST_CANCEL = 62 << MSG_SHIFT; private static final int MSG_SET_BIOMETRICS_LISTENER = 63 << MSG_SHIFT; + private static final int MSG_MEDIA_TRANSFER_SENDER_STATE = 64 << MSG_SHIFT; + private static final int MSG_MEDIA_TRANSFER_RECEIVER_STATE = 65 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -439,6 +443,17 @@ public class CommandQueue extends IStatusBar.Stub implements * @see IStatusBar#cancelRequestAddTile */ default void cancelRequestAddTile(@NonNull String packageName) {} + + /** @see IStatusBar#updateMediaTapToTransferSenderDisplay */ + default void updateMediaTapToTransferSenderDisplay( + @StatusBarManager.MediaTransferSenderState int displayState, + @NonNull MediaRoute2Info routeInfo, + @Nullable IUndoMediaTransferCallback undoCallback) {} + + /** @see IStatusBar#updateMediaTapToTransferReceiverDisplay */ + default void updateMediaTapToTransferReceiverDisplay( + @StatusBarManager.MediaTransferReceiverState int displayState, + @NonNull MediaRoute2Info routeInfo) {} } public CommandQueue(Context context) { @@ -1177,6 +1192,29 @@ public class CommandQueue extends IStatusBar.Stub implements mHandler.obtainMessage(MSG_TILE_SERVICE_REQUEST_CANCEL, s).sendToTarget(); } + @Override + public void updateMediaTapToTransferSenderDisplay( + @StatusBarManager.MediaTransferSenderState int displayState, + MediaRoute2Info routeInfo, + IUndoMediaTransferCallback undoCallback + ) throws RemoteException { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = displayState; + args.arg2 = routeInfo; + args.arg3 = undoCallback; + mHandler.obtainMessage(MSG_MEDIA_TRANSFER_SENDER_STATE, args).sendToTarget(); + } + + @Override + public void updateMediaTapToTransferReceiverDisplay( + int displayState, + MediaRoute2Info routeInfo) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = displayState; + args.arg2 = routeInfo; + mHandler.obtainMessage(MSG_MEDIA_TRANSFER_RECEIVER_STATE, args).sendToTarget(); + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -1574,6 +1612,29 @@ public class CommandQueue extends IStatusBar.Stub implements for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).cancelRequestAddTile(packageName); } + break; + case MSG_MEDIA_TRANSFER_SENDER_STATE: + args = (SomeArgs) msg.obj; + int displayState = (int) args.arg1; + MediaRoute2Info routeInfo = (MediaRoute2Info) args.arg2; + IUndoMediaTransferCallback undoCallback = + (IUndoMediaTransferCallback) args.arg3; + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).updateMediaTapToTransferSenderDisplay( + displayState, routeInfo, undoCallback); + } + args.recycle(); + break; + case MSG_MEDIA_TRANSFER_RECEIVER_STATE: + args = (SomeArgs) msg.obj; + int receiverDisplayState = (int) args.arg1; + MediaRoute2Info receiverRouteInfo = (MediaRoute2Info) args.arg2; + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).updateMediaTapToTransferReceiverDisplay( + receiverDisplayState, receiverRouteInfo); + } + args.recycle(); + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 391525e11866..092e86daddb8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -179,6 +179,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) { return; } + Trace.beginSection("NotificationViewHierarchyManager.updateNotificationViews"); beginUpdate(); @@ -353,6 +354,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle mListContainer.onNotificationViewUpdateFinished(); endUpdate(); + Trace.endSection(); } /** @@ -362,6 +364,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle * {@link com.android.systemui.statusbar.notification.collection.coordinator.StackCoordinator} */ private void updateNotifStats() { + Trace.beginSection("NotificationViewHierarchyManager.updateNotifStats"); boolean hasNonClearableAlertingNotifs = false; boolean hasClearableAlertingNotifs = false; boolean hasNonClearableSilentNotifs = false; @@ -403,6 +406,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle hasNonClearableSilentNotifs /* hasNonClearableSilentNotifs */, hasClearableSilentNotifs /* hasClearableSilentNotifs */ )); + Trace.endSection(); } /** @@ -520,7 +524,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle } private void updateRowStatesInternal() { - Trace.beginSection("NotificationViewHierarchyManager#updateRowStates"); + Trace.beginSection("NotificationViewHierarchyManager.updateRowStates"); final int N = mListContainer.getContainerChildCount(); int visibleNotifications = 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index c33160858d0f..ad9f12ec3bc0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -24,6 +24,7 @@ import android.app.Notification; import android.app.NotificationChannel; import android.os.RemoteException; import android.os.SystemClock; +import android.os.Trace; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; @@ -343,11 +344,14 @@ public class NotificationEntryManager implements private final InflationCallback mInflationCallback = new InflationCallback() { @Override public void handleInflationException(NotificationEntry entry, Exception e) { + Trace.beginSection("NotificationEntryManager.handleInflationException"); NotificationEntryManager.this.handleInflationException(entry.getSbn(), e); + Trace.endSection(); } @Override public void onAsyncInflationFinished(NotificationEntry entry) { + Trace.beginSection("NotificationEntryManager.onAsyncInflationFinished"); mPendingNotifications.remove(entry.getKey()); // If there was an async task started after the removal, we don't want to add it back to // the list, otherwise we might get leaks. @@ -369,6 +373,7 @@ public class NotificationEntryManager implements } } } + Trace.endSection(); } }; @@ -463,6 +468,7 @@ public class NotificationEntryManager implements boolean forceRemove, DismissedByUserStats dismissedByUserStats, int reason) { + Trace.beginSection("NotificationEntryManager.removeNotificationInternal"); final NotificationEntry entry = getActiveNotificationUnfiltered(key); @@ -470,6 +476,7 @@ public class NotificationEntryManager implements if (interceptor.onNotificationRemoveRequested(key, entry, reason)) { // Remove intercepted; log and skip mLogger.logRemovalIntercepted(key); + Trace.endSection(); return; } } @@ -557,6 +564,7 @@ public class NotificationEntryManager implements mLeakDetector.trackGarbage(entry); } } + Trace.endSection(); } private void sendNotificationRemovalToServer( @@ -620,6 +628,7 @@ public class NotificationEntryManager implements private void addNotificationInternal( StatusBarNotification notification, RankingMap rankingMap) throws InflationException { + Trace.beginSection("NotificationEntryManager.addNotificationInternal"); String key = notification.getKey(); if (DEBUG) { Log.d(TAG, "addNotification key=" + key); @@ -667,6 +676,7 @@ public class NotificationEntryManager implements for (NotifCollectionListener listener : mNotifCollectionListeners) { listener.onRankingApplied(); } + Trace.endSection(); } public void addNotification(StatusBarNotification notification, RankingMap ranking) { @@ -679,12 +689,14 @@ public class NotificationEntryManager implements private void updateNotificationInternal(StatusBarNotification notification, RankingMap ranking) throws InflationException { + Trace.beginSection("NotificationEntryManager.updateNotificationInternal"); if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); final String key = notification.getKey(); abortExistingInflation(key, "updateNotification"); final NotificationEntry entry = getActiveNotificationUnfiltered(key); if (entry == null) { + Trace.endSection(); return; } @@ -721,6 +733,7 @@ public class NotificationEntryManager implements for (NotifCollectionListener listener : mNotifCollectionListeners) { listener.onRankingApplied(); } + Trace.endSection(); } public void updateNotification(StatusBarNotification notification, RankingMap ranking) { @@ -740,14 +753,17 @@ public class NotificationEntryManager implements mLogger.logUseWhileNewPipelineActive("updateNotifications", reason); return; } + Trace.beginSection("NotificationEntryManager.updateNotifications"); reapplyFilterAndSort(reason); if (mPresenter != null) { mPresenter.updateNotificationViews(reason); } mNotifLiveDataStore.setActiveNotifList(getVisibleNotifications()); + Trace.endSection(); } public void updateNotificationRanking(RankingMap rankingMap) { + Trace.beginSection("NotificationEntryManager.updateNotificationRanking"); List<NotificationEntry> entries = new ArrayList<>(); entries.addAll(getVisibleNotifications()); entries.addAll(mPendingNotifications.values()); @@ -788,6 +804,7 @@ public class NotificationEntryManager implements for (NotifCollectionListener listener : mNotifCollectionListeners) { listener.onRankingApplied(); } + Trace.endSection(); } void notifyChannelModified( @@ -887,6 +904,7 @@ public class NotificationEntryManager implements /** @return list of active notifications filtered for the current user */ public List<NotificationEntry> getActiveNotificationsForCurrentUser() { + Trace.beginSection("NotificationEntryManager.getActiveNotificationsForCurrentUser"); Assert.isMainThread(); ArrayList<NotificationEntry> filtered = new ArrayList<>(); @@ -898,7 +916,7 @@ public class NotificationEntryManager implements } filtered.add(entry); } - + Trace.endSection(); return filtered; } @@ -908,10 +926,12 @@ public class NotificationEntryManager implements * @param reason the reason for calling this method, which will be logged */ public void updateRanking(RankingMap rankingMap, String reason) { + Trace.beginSection("NotificationEntryManager.updateRanking"); updateRankingAndSort(rankingMap, reason); for (NotifCollectionListener listener : mNotifCollectionListeners) { listener.onRankingApplied(); } + Trace.endSection(); } /** Resorts / filters the current notification set with the current RankingMap */ @@ -920,7 +940,9 @@ public class NotificationEntryManager implements mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason); return; } + Trace.beginSection("NotificationEntryManager.reapplyFilterAndSort"); updateRankingAndSort(mRanker.getRankingMap(), reason); + Trace.endSection(); } /** Calls to NotificationRankingManager and updates mSortedAndFiltered */ @@ -929,9 +951,11 @@ public class NotificationEntryManager implements mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason); return; } + Trace.beginSection("NotificationEntryManager.updateRankingAndSort"); mSortedAndFiltered.clear(); mSortedAndFiltered.addAll(mRanker.updateRanking( rankingMap, mActiveNotifications.values(), reason)); + Trace.endSection(); } /** dump the current active notification list. Called from StatusBar */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 74c97fdbddca..31d9f09d1569 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -426,14 +426,18 @@ public class ShadeListBuilder implements Dumpable { } Trace.endSection(); + Trace.beginSection("ShadeListBuilder.logEndBuildList"); // Step 9: We're done! mLogger.logEndBuildList( mIterationCount, mReadOnlyNotifList.size(), countChildren(mReadOnlyNotifList)); if (mAlwaysLogList || mIterationCount % 10 == 0) { + Trace.beginSection("ShadeListBuilder.logFinalList"); mLogger.logFinalList(mNotifList); + Trace.endSection(); } + Trace.endSection(); mPipelineState.setState(STATE_IDLE); mIterationCount++; Trace.endSection(); @@ -996,16 +1000,20 @@ public class ShadeListBuilder implements Dumpable { } private void freeEmptyGroups() { + Trace.beginSection("ShadeListBuilder.freeEmptyGroups"); mGroups.values().removeIf(ge -> ge.getSummary() == null && ge.getChildren().isEmpty()); + Trace.endSection(); } private void logChanges() { + Trace.beginSection("ShadeListBuilder.logChanges"); for (NotificationEntry entry : mAllEntries) { logAttachStateChanges(entry); } for (GroupEntry group : mGroups.values()) { logAttachStateChanges(group); } + Trace.endSection(); } private void logAttachStateChanges(ListEntry entry) { @@ -1083,6 +1091,7 @@ public class ShadeListBuilder implements Dumpable { } private void cleanupPluggables() { + Trace.beginSection("ShadeListBuilder.cleanupPluggables"); callOnCleanup(mNotifPreGroupFilters); callOnCleanup(mNotifPromoters); callOnCleanup(mNotifFinalizeFilters); @@ -1093,6 +1102,7 @@ public class ShadeListBuilder implements Dumpable { } callOnCleanup(List.of(getStabilityManager())); + Trace.endSection(); } private void callOnCleanup(List<? extends Pluggable<?>> pluggables) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index c6a8a69cfb0d..1c96e8ceb27f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -23,6 +23,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStackC import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.phone.NotificationIconAreaController +import com.android.systemui.util.traceSection import javax.inject.Inject /** @@ -38,10 +39,11 @@ class StackCoordinator @Inject internal constructor( pipeline.addOnAfterRenderListListener(::onAfterRenderList) } - fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) { - controller.setNotifStats(calculateNotifStats(entries)) - notificationIconAreaController.updateNotificationIcons(entries) - } + fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) = + traceSection("StackCoordinator.onAfterRenderList") { + controller.setNotifStats(calculateNotifStats(entries)) + notificationIconAreaController.updateNotificationIcons(entries) + } private fun calculateNotifStats(entries: List<ListEntry>): NotifStats { var hasNonClearableAlertingNotifs = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index b02dc0cffdb9..54e26c34522d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -39,6 +39,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.children import com.android.systemui.util.foldToSparseArray import com.android.systemui.util.takeUntil +import com.android.systemui.util.traceSection import javax.inject.Inject /** @@ -157,7 +158,9 @@ class NotificationSectionsManager @Inject internal constructor( } } } - private fun logShadeContents() = parent.children.forEachIndexed(::logShadeChild) + private fun logShadeContents() = traceSection("NotifSectionsManager.logShadeContents") { + parent.children.forEachIndexed(::logShadeChild) + } private val isUsingMultipleSections: Boolean get() = sectionsFeatureManager.getNumberOfBuckets() > 1 @@ -221,10 +224,10 @@ class NotificationSectionsManager @Inject internal constructor( * Should be called whenever notifs are added, removed, or updated. Updates section boundary * bookkeeping and adds/moves/removes section headers if appropriate. */ - fun updateSectionBoundaries(reason: String) { + fun updateSectionBoundaries(reason: String) = traceSection("NotifSectionsManager.update") { notifPipelineFlags.checkLegacyPipelineEnabled() if (!isUsingMultipleSections) { - return + return@traceSection } logger.logStartSectionUpdate(reason) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index dd72615768b0..25b8a6545638 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -43,7 +43,6 @@ import android.graphics.Path; import android.graphics.PointF; import android.graphics.Rect; import android.os.Bundle; -import android.os.UserHandle; import android.provider.Settings; import android.util.AttributeSet; import android.util.IndentingPrintWriter; @@ -696,8 +695,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable && mQsExpansionFraction != 1 && !mScreenOffAnimationController.shouldHideNotificationsFooter() && !mIsRemoteInputActive; - boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1; + boolean showHistory = mController.isHistoryEnabled(); updateFooterView(showFooterView, showDismissView, showHistory); } @@ -5243,11 +5241,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable R.layout.status_bar_no_notifications, this, false); view.setText(R.string.empty_shade_text); view.setOnClickListener(v -> { - final boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1; - Intent intent = showHistory ? new Intent( - Settings.ACTION_NOTIFICATION_HISTORY) : new Intent( - Settings.ACTION_NOTIFICATION_SETTINGS); + final boolean showHistory = mController.isHistoryEnabled(); + Intent intent = showHistory + ? new Intent(Settings.ACTION_NOTIFICATION_HISTORY) + : new Intent(Settings.ACTION_NOTIFICATION_SETTINGS); mStatusBar.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP); }); setEmptyShadeView(view); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 334128a2b4ca..a2929f01f715 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -35,6 +35,8 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; +import android.os.Trace; +import android.os.UserHandle; import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -180,7 +182,6 @@ public class NotificationStackScrollLayoutController { private final NotificationLockscreenUserManager mLockscreenUserManager; // TODO: StatusBar should be encapsulated behind a Controller private final StatusBar mStatusBar; - private final NotificationGroupManagerLegacy mLegacyGroupManager; private final SectionHeaderController mSilentHeaderController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private final InteractionJankMonitor mJankMonitor; @@ -191,6 +192,7 @@ public class NotificationStackScrollLayoutController { private boolean mFadeNotificationsOnDismiss; private NotificationSwipeHelper mSwipeHelper; private boolean mShowEmptyShadeView; + @Nullable private Boolean mHistoryEnabled; private int mBarState; private HeadsUpAppearanceController mHeadsUpAppearanceController; @@ -340,6 +342,8 @@ public class NotificationStackScrollLayoutController { @Override public void onUserChanged(int userId) { mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode()); + mHistoryEnabled = null; + updateFooter(); } }; @@ -699,8 +703,6 @@ public class NotificationStackScrollLayoutController { } }); mNotifPipelineFlags = notifPipelineFlags; - mLegacyGroupManager = mNotifPipelineFlags.isNewPipelineEnabled() - ? null : legacyGroupManager; mSilentHeaderController = silentHeaderController; mNotifPipeline = notifPipeline; mNotifCollection = notifCollection; @@ -802,6 +804,7 @@ public class NotificationStackScrollLayoutController { (key, newValue) -> { switch (key) { case Settings.Secure.NOTIFICATION_HISTORY_ENABLED: + mHistoryEnabled = null; // invalidate updateFooter(); break; case HIGH_PRIORITY: @@ -1009,6 +1012,20 @@ public class NotificationStackScrollLayoutController { return mNotifStats.getNumActiveNotifs(); } + public boolean isHistoryEnabled() { + Boolean historyEnabled = mHistoryEnabled; + if (historyEnabled == null) { + if (mView == null || mView.getContext() == null) { + Log.wtf(TAG, "isHistoryEnabled failed to initialize its value"); + return false; + } + mHistoryEnabled = historyEnabled = + Settings.Secure.getIntForUser(mView.getContext().getContentResolver(), + Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1; + } + return historyEnabled; + } + public int getIntrinsicContentHeight() { return mView.getIntrinsicContentHeight(); } @@ -1199,6 +1216,7 @@ public class NotificationStackScrollLayoutController { * are true. */ public void updateShowEmptyShadeView() { + Trace.beginSection("NSSLC.updateShowEmptyShadeView"); mShowEmptyShadeView = mBarState != KEYGUARD && (!mView.isQsExpanded() || mView.isUsingSplitNotificationShade()) && getVisibleNotificationCount() == 0; @@ -1206,6 +1224,7 @@ public class NotificationStackScrollLayoutController { mView.updateEmptyShadeView( mShowEmptyShadeView, mZenModeController.areNotificationsHiddenInShade()); + Trace.endSection(); } public boolean areNotificationsHiddenInShade() { @@ -1323,11 +1342,15 @@ public class NotificationStackScrollLayoutController { if (mNotifPipelineFlags.isNewPipelineEnabled()) { return; } + Trace.beginSection("NSSLC.updateSectionBoundaries"); mView.updateSectionBoundaries(reason); + Trace.endSection(); } public void updateFooter() { + Trace.beginSection("NSSLC.updateFooter"); mView.updateFooter(); + Trace.endSection(); } public void onUpdateRowStates() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt index 1a885336be5e..b457ebf60c71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt @@ -1,6 +1,8 @@ package com.android.systemui.statusbar.phone import android.view.WindowInsets +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.plugins.qs.QS import com.android.systemui.plugins.qs.QSContainerController @@ -14,7 +16,8 @@ import javax.inject.Inject class NotificationsQSContainerController @Inject constructor( view: NotificationsQuickSettingsContainer, private val navigationModeController: NavigationModeController, - private val overviewProxyService: OverviewProxyService + private val overviewProxyService: OverviewProxyService, + private val featureFlags: FeatureFlags ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController { var qsExpanded = false @@ -108,7 +111,11 @@ class NotificationsQSContainerController @Inject constructor( } mView.setPadding(0, 0, 0, containerPadding) mView.setNotificationsMarginBottom(notificationsMargin) - mView.setQSScrollPaddingBottom(qsScrollPaddingBottom) + if (featureFlags.isEnabled(Flags.NEW_FOOTER)) { + mView.setQSContainerPaddingBottom(notificationsMargin) + } else { + mView.setQSScrollPaddingBottom(qsScrollPaddingBottom) + } } private fun calculateBottomSpacing(): Pair<Int, Int> { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index cecbcdb829c6..95e3b70b4c5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -52,6 +52,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout private Consumer<QS> mQSFragmentAttachedListener = qs -> {}; private QS mQs; private View mQSScrollView; + private View mQSContainer; public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) { super(context, attrs); @@ -70,6 +71,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout mQs = (QS) fragment; mQSFragmentAttachedListener.accept(mQs); mQSScrollView = mQs.getView().findViewById(R.id.expanded_qs_scroll_view); + mQSContainer = mQs.getView().findViewById(R.id.quick_settings_container); } @Override @@ -89,6 +91,16 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout mQSScrollView.getPaddingLeft(), mQSScrollView.getPaddingTop(), mQSScrollView.getPaddingRight(), + paddingBottom); + } + } + + public void setQSContainerPaddingBottom(int paddingBottom) { + if (mQSContainer != null) { + mQSContainer.setPadding( + mQSContainer.getPaddingLeft(), + mQSContainer.getPaddingTop(), + mQSContainer.getPaddingRight(), paddingBottom ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index 81ae2093c830..5f800eb80ec2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -21,9 +21,6 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver -import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService -import com.android.systemui.shared.mediattt.DeviceInfo -import com.android.systemui.shared.mediattt.IDeviceSenderService import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.util.mockito.any @@ -54,19 +51,10 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { @Mock private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver - @Mock - private lateinit var mediaSenderService: IDeviceSenderService.Stub - private lateinit var mediaSenderServiceComponentName: ComponentName @Before fun setUp() { MockitoAnnotations.initMocks(this) - - mediaSenderServiceComponentName = ComponentName(context, MediaTttSenderService::class.java) - context.addMockService(mediaSenderServiceComponentName, mediaSenderService) - whenever(mediaSenderService.queryLocalInterface(anyString())).thenReturn(mediaSenderService) - whenever(mediaSenderService.asBinder()).thenReturn(mediaSenderService) - mediaTttCommandLineHelper = MediaTttCommandLineHelper( commandRegistry, @@ -100,6 +88,7 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { ) { EmptyCommand() } } + /* TODO(b/216318437): Revive these tests using the new SystemApis. @Test fun sender_moveCloserToStartCast_serviceCallbackCalled() { commandRegistry.onShellCommand(pw, getMoveCloserToStartCastCommand()) @@ -182,6 +171,8 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { verify(mediaSenderService).noLongerCloseToReceiver(any(), any()) } + */ + @Test fun receiver_addCommand_chipAdded() { commandRegistry.onShellCommand(pw, arrayOf(ADD_CHIP_COMMAND_RECEIVER_TAG)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index 6b4eebe680f9..58f4818b3de5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -24,9 +24,10 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import androidx.test.filters.SmallTest +import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.shared.mediattt.IUndoTransferCallback +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -46,12 +47,14 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Mock private lateinit var windowManager: WindowManager + @Mock + private lateinit var commandQueue: CommandQueue @Before fun setUp() { MockitoAnnotations.initMocks(this) appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) - controllerSender = MediaTttChipControllerSender(context, windowManager) + controllerSender = MediaTttChipControllerSender(context, windowManager, commandQueue) } @Test @@ -133,7 +136,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() { - val undoCallback = object : IUndoTransferCallback.Stub() { + val undoCallback = object : IUndoMediaTransferCallback.Stub() { override fun onUndoTriggered() {} } controllerSender.displayChip(transferToReceiverSucceeded(undoCallback)) @@ -146,7 +149,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() { var undoCallbackCalled = false - val undoCallback = object : IUndoTransferCallback.Stub() { + val undoCallback = object : IUndoMediaTransferCallback.Stub() { override fun onUndoTriggered() { undoCallbackCalled = true } @@ -160,7 +163,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() { - val undoCallback = object : IUndoTransferCallback.Stub() { + val undoCallback = object : IUndoMediaTransferCallback.Stub() { override fun onUndoTriggered() {} } controllerSender.displayChip(transferToReceiverSucceeded(undoCallback)) @@ -194,7 +197,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun transferToThisDeviceSucceeded_withUndoRunnable_undoWithClick() { - val undoCallback = object : IUndoTransferCallback.Stub() { + val undoCallback = object : IUndoMediaTransferCallback.Stub() { override fun onUndoTriggered() {} } controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback)) @@ -207,7 +210,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun transferToThisDeviceSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() { var undoCallbackCalled = false - val undoCallback = object : IUndoTransferCallback.Stub() { + val undoCallback = object : IUndoMediaTransferCallback.Stub() { override fun onUndoTriggered() { undoCallbackCalled = true } @@ -221,7 +224,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() { - val undoCallback = object : IUndoTransferCallback.Stub() { + val undoCallback = object : IUndoMediaTransferCallback.Stub() { override fun onUndoTriggered() {} } controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback)) @@ -267,7 +270,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(transferToReceiverTriggered()) controllerSender.displayChip( transferToReceiverSucceeded( - object : IUndoTransferCallback.Stub() { + object : IUndoMediaTransferCallback.Stub() { override fun onUndoTriggered() {} } ) @@ -327,13 +330,13 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { TransferToThisDeviceTriggered(appIconDrawable, APP_ICON_CONTENT_DESC) /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferToReceiverSucceeded(undoCallback: IUndoTransferCallback? = null) = + private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) = TransferToReceiverSucceeded( appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback ) /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferToThisDeviceSucceeded(undoCallback: IUndoTransferCallback? = null) = + private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) = TransferToThisDeviceSucceeded( appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt deleted file mode 100644 index 64542cb2f647..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt +++ /dev/null @@ -1,126 +0,0 @@ -package com.android.systemui.media.taptotransfer.sender - -import android.media.MediaRoute2Info -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.shared.mediattt.DeviceInfo -import com.android.systemui.shared.mediattt.IDeviceSenderService -import com.android.systemui.shared.mediattt.IUndoTransferCallback -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.argumentCaptor -import com.android.systemui.util.mockito.capture -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Ignore -import org.junit.Test -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@SmallTest -@Ignore("b/216286227") -class MediaTttSenderServiceTest : SysuiTestCase() { - - private lateinit var service: IDeviceSenderService - - @Mock - private lateinit var controller: MediaTttChipControllerSender - - private val mediaInfo = MediaRoute2Info.Builder("id", "Test Name") - .addFeature("feature") - .build() - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - val mediaTttSenderService = MediaTttSenderService(context, controller) - service = IDeviceSenderService.Stub.asInterface(mediaTttSenderService.onBind(null)) - } - - @Test - fun closeToReceiverToStartCast_controllerTriggeredWithCorrectState() { - val name = "Fake name" - service.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name)) - - val chipStateCaptor = argumentCaptor<MoveCloserToStartCast>() - verify(controller).displayChip(capture(chipStateCaptor)) - - val chipState = chipStateCaptor.value!! - assertThat(chipState.getChipTextString(context)).contains(name) - } - - @Test - fun closeToReceiverToEndCast_controllerTriggeredWithCorrectState() { - val name = "Fake name" - service.closeToReceiverToEndCast(mediaInfo, DeviceInfo(name)) - - val chipStateCaptor = argumentCaptor<MoveCloserToEndCast>() - verify(controller).displayChip(capture(chipStateCaptor)) - - val chipState = chipStateCaptor.value!! - assertThat(chipState.getChipTextString(context)).contains(name) - } - - @Test - fun transferToThisDeviceTriggered_controllerTriggeredWithCorrectState() { - service.transferToThisDeviceTriggered(mediaInfo, DeviceInfo("Fake name")) - - verify(controller).displayChip(any<TransferToThisDeviceTriggered>()) - } - - @Test - fun transferToReceiverTriggered_controllerTriggeredWithCorrectState() { - val name = "Fake name" - service.transferToReceiverTriggered(mediaInfo, DeviceInfo(name)) - - val chipStateCaptor = argumentCaptor<TransferToReceiverTriggered>() - verify(controller).displayChip(capture(chipStateCaptor)) - - val chipState = chipStateCaptor.value!! - assertThat(chipState.getChipTextString(context)).contains(name) - } - - @Test - fun transferToReceiverSucceeded_controllerTriggeredWithCorrectState() { - val name = "Fake name" - val undoCallback = object : IUndoTransferCallback.Stub() { - override fun onUndoTriggered() {} - } - service.transferToReceiverSucceeded(mediaInfo, DeviceInfo(name), undoCallback) - - val chipStateCaptor = argumentCaptor<TransferToReceiverSucceeded>() - verify(controller).displayChip(capture(chipStateCaptor)) - - val chipState = chipStateCaptor.value!! - assertThat(chipState.getChipTextString(context)).contains(name) - assertThat(chipState.undoCallback).isEqualTo(undoCallback) - } - - @Test - fun transferToThisDeviceSucceeded_controllerTriggeredWithCorrectState() { - val undoCallback = object : IUndoTransferCallback.Stub() { - override fun onUndoTriggered() {} - } - service.transferToThisDeviceSucceeded(mediaInfo, DeviceInfo("name"), undoCallback) - - val chipStateCaptor = argumentCaptor<TransferToThisDeviceSucceeded>() - verify(controller).displayChip(capture(chipStateCaptor)) - - val chipState = chipStateCaptor.value!! - assertThat(chipState.undoCallback).isEqualTo(undoCallback) - } - - @Test - fun transferFailed_controllerTriggeredWithTransferFailedState() { - service.transferFailed(mediaInfo, DeviceInfo("Fake name")) - - verify(controller).displayChip(any<TransferFailed>()) - } - - @Test - fun noLongerCloseToReceiver_controllerRemoveChipTriggered() { - service.noLongerCloseToReceiver(mediaInfo, DeviceInfo("Fake name")) - - verify(controller).removeChip() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt index 354bb5192251..f5fa0d030901 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt @@ -15,9 +15,10 @@ import com.android.internal.logging.testing.FakeMetricsLogger import com.android.systemui.Dependency import com.android.systemui.R import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.globalactions.GlobalActionsDialogLite import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.qs.FooterActionsController.ExpansionState import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.MultiUserSwitchController import com.android.systemui.statusbar.policy.DeviceProvisionedController @@ -54,12 +55,16 @@ class FooterActionsControllerTest : LeakCheckedTest() { @Mock private lateinit var userInfoController: UserInfoController @Mock + private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory + @Mock private lateinit var multiUserSwitchController: MultiUserSwitchController @Mock private lateinit var globalActionsDialog: GlobalActionsDialogLite @Mock private lateinit var uiEventLogger: UiEventLogger @Mock + private lateinit var featureFlags: FeatureFlags + private lateinit var controller: FooterActionsController private val metricsLogger: MetricsLogger = FakeMetricsLogger() @@ -76,15 +81,18 @@ class FooterActionsControllerTest : LeakCheckedTest() { injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES) val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService + whenever(multiUserSwitchControllerFactory.create(any())) + .thenReturn(multiUserSwitchController) + whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(false) + view = LayoutInflater.from(context) .inflate(R.layout.footer_actions, null) as FooterActionsView - controller = FooterActionsController(view, activityStarter, - userManager, userTracker, userInfoController, multiUserSwitchController, + controller = FooterActionsController(view, multiUserSwitchControllerFactory, + activityStarter, userManager, userTracker, userInfoController, deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService, - globalActionsDialog, uiEventLogger, showPMLiteButton = true, - buttonsVisibleState = ExpansionState.EXPANDED, fakeSettings, - Handler(testableLooper.looper)) + globalActionsDialog, uiEventLogger, showPMLiteButton = true, fakeSettings, + Handler(testableLooper.looper), featureFlags) controller.init() ViewUtils.attachView(view) // View looper is the testable looper associated with the test @@ -98,7 +106,7 @@ class FooterActionsControllerTest : LeakCheckedTest() { @Test fun testLogPowerMenuClick() { - controller.expanded = true + controller.visible = true falsingManager.setFalseTap(false) view.findViewById<View>(R.id.pm_lite).performClick() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java index f43e68f3e575..26aa37df0c0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java @@ -61,12 +61,8 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { @Mock private ClipboardManager mClipboardManager; @Mock - private QuickQSPanelController mQuickQSPanelController; - @Mock private TextView mBuildText; @Mock - private FooterActionsController mFooterActionsController; - @Mock private FalsingManager mFalsingManager; @Mock private ActivityStarter mActivityStarter; @@ -93,8 +89,7 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { when(mView.findViewById(android.R.id.edit)).thenReturn(mEditButton); mController = new QSFooterViewController(mView, mUserTracker, mFalsingManager, - mActivityStarter, mQSPanelController, mQuickQSPanelController, - mFooterActionsController); + mActivityStarter, mQSPanelController); mController.init(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index 59948d310b4f..09c6d9e86a44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -68,8 +68,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() { private lateinit var tileView: QSTileView @Mock private lateinit var quickQsBrightnessController: QuickQSBrightnessController - @Mock - private lateinit var footerActionsController: FooterActionsController @Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener> @@ -95,8 +93,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() { uiEventLogger, qsLogger, dumpManager, - quickQsBrightnessController, - footerActionsController + quickQsBrightnessController ) controller.init() @@ -128,14 +125,12 @@ class QuickQSPanelControllerTest : SysuiTestCase() { } @Test - fun testBrightnessAndFooterVisibilityRefreshedWhenConfigurationChanged() { + fun testBrightnessRefreshedWhenConfigurationChanged() { // times(2) because both controller and base controller are registering their listeners verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture()) captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) } verify(quickQsBrightnessController).refreshVisibility(anyBoolean()) - // times(2) because footer visibility is also refreshed on controller init - verify(footerActionsController, times(2)).refreshVisibility(anyBoolean()) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index bdcbbbc99ea3..4f731ed5f72c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.stack; -import static android.provider.Settings.Secure.NOTIFICATION_HISTORY_ENABLED; import static android.view.View.GONE; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; @@ -41,8 +40,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.os.UserHandle; -import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.MathUtils; @@ -112,10 +109,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { public void setUp() throws Exception { allowTestableLooperAsMainThread(); - Settings.Secure.putIntForUser(mContext.getContentResolver(), NOTIFICATION_HISTORY_ENABLED, - 1, UserHandle.USER_CURRENT); - - // Interact with real instance of AmbientState. mAmbientState = new AmbientState(mContext, mNotificationSectionsManager, mBypassController); @@ -150,6 +143,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mStackScroller.setShelfController(notificationShelfController); mStackScroller.setStatusBar(mBar); mStackScroller.setEmptyShadeView(mEmptyShadeView); + when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true); when(mStackScrollLayoutController.getNoticationRoundessManager()) .thenReturn(mNotificationRoundnessManager); mStackScroller.setController(mStackScrollLayoutController); @@ -404,6 +398,22 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test + public void testUpdateFooter_withoutHistory() { + setBarStateForTest(StatusBarState.SHADE); + mStackScroller.setCurrentUserSetup(true); + + when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(false); + when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); + when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) + .thenReturn(true); + + FooterView view = mock(FooterView.class); + mStackScroller.setFooterView(view); + mStackScroller.updateFooter(); + verify(mStackScroller).updateFooterView(true, true, false); + } + + @Test public void testUpdateFooter_oneClearableNotification_beforeUserSetup() { setBarStateForTest(StatusBarState.SHADE); mStackScroller.setCurrentUserSetup(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt index 77e90256ab70..bbb2346797b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt @@ -6,6 +6,8 @@ import android.view.WindowInsets import android.view.WindowManagerPolicyConstants import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener import com.android.systemui.recents.OverviewProxyService @@ -46,6 +48,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { private lateinit var overviewProxyService: OverviewProxyService @Mock private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer + @Mock + private lateinit var featureFlags: FeatureFlags @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener> @Captor @@ -64,7 +68,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { notificationsQSContainerController = NotificationsQSContainerController( notificationsQSContainer, navigationModeController, - overviewProxyService + overviewProxyService, + featureFlags ) whenever(notificationsQSContainer.defaultNotificationsMarginBottom) .thenReturn(NOTIFICATIONS_MARGIN) @@ -85,6 +90,26 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testTaskbarVisibleInSplitShade() { notificationsQSContainerController.splitShadeEnabled = true + useNewFooter(false) + + given(taskbarVisible = true, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded + expectedNotificationsMargin = NOTIFICATIONS_MARGIN) + + given(taskbarVisible = true, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = STABLE_INSET_BOTTOM, + expectedNotificationsMargin = NOTIFICATIONS_MARGIN) + } + + @Test + fun testTaskbarVisibleInSplitShade_newFooter() { + notificationsQSContainerController.splitShadeEnabled = true + useNewFooter(true) + given(taskbarVisible = true, navigationMode = GESTURES_NAVIGATION, insets = windowInsets().withStableBottom()) @@ -102,6 +127,26 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { fun testTaskbarNotVisibleInSplitShade() { // when taskbar is not visible, it means we're on the home screen notificationsQSContainerController.splitShadeEnabled = true + useNewFooter(false) + + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons + expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN) + } + + @Test + fun testTaskbarNotVisibleInSplitShade_newFooter() { + // when taskbar is not visible, it means we're on the home screen + notificationsQSContainerController.splitShadeEnabled = true + useNewFooter(true) + given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = windowInsets().withStableBottom()) @@ -117,6 +162,25 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testTaskbarNotVisibleInSplitShadeWithCutout() { notificationsQSContainerController.splitShadeEnabled = true + useNewFooter(false) + + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withCutout()) + then(expectedContainerPadding = CUTOUT_HEIGHT) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withCutout().withStableBottom()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN) + } + + @Test + fun testTaskbarNotVisibleInSplitShadeWithCutout_newFooter() { + notificationsQSContainerController.splitShadeEnabled = true + useNewFooter(true) + given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = windowInsets().withCutout()) @@ -132,6 +196,24 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testTaskbarVisibleInSinglePaneShade() { notificationsQSContainerController.splitShadeEnabled = false + useNewFooter(false) + + given(taskbarVisible = true, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0) + + given(taskbarVisible = true, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = STABLE_INSET_BOTTOM) + } + + @Test + fun testTaskbarVisibleInSinglePaneShade_newFooter() { + notificationsQSContainerController.splitShadeEnabled = false + useNewFooter(true) + given(taskbarVisible = true, navigationMode = GESTURES_NAVIGATION, insets = windowInsets().withStableBottom()) @@ -146,6 +228,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testTaskbarNotVisibleInSinglePaneShade() { notificationsQSContainerController.splitShadeEnabled = false + useNewFooter(false) + given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets()) @@ -159,14 +243,56 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = windowInsets().withStableBottom()) - then(expectedContainerPadding = 0, - expectedQsPadding = STABLE_INSET_BOTTOM) + then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM) + } + + @Test + fun testTaskbarNotVisibleInSinglePaneShade_newFooter() { + notificationsQSContainerController.splitShadeEnabled = false + useNewFooter(true) + + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = emptyInsets()) + then(expectedContainerPadding = 0) + + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withCutout().withStableBottom()) + then(expectedContainerPadding = CUTOUT_HEIGHT) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM) } @Test fun testCustomizingInSinglePaneShade() { notificationsQSContainerController.splitShadeEnabled = false notificationsQSContainerController.setCustomizerShowing(true) + useNewFooter(false) + + // always sets spacings to 0 + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = 0) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = emptyInsets()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = 0) + } + + @Test + fun testCustomizingInSinglePaneShade_newFooter() { + notificationsQSContainerController.splitShadeEnabled = false + notificationsQSContainerController.setCustomizerShowing(true) + useNewFooter(true) + // always sets spacings to 0 given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, @@ -185,6 +311,28 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { fun testDetailShowingInSinglePaneShade() { notificationsQSContainerController.splitShadeEnabled = false notificationsQSContainerController.setDetailShowing(true) + useNewFooter(false) + + // always sets spacings to 0 + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = 0) + + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = emptyInsets()) + then(expectedContainerPadding = 0, + expectedNotificationsMargin = 0) + } + + @Test + fun testDetailShowingInSinglePaneShade_newFooter() { + notificationsQSContainerController.splitShadeEnabled = false + notificationsQSContainerController.setDetailShowing(true) + useNewFooter(true) + // always sets spacings to 0 given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, @@ -202,6 +350,26 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testDetailShowingInSplitShade() { notificationsQSContainerController.splitShadeEnabled = true + useNewFooter(false) + + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = windowInsets().withStableBottom()) + then(expectedContainerPadding = 0) + + notificationsQSContainerController.setDetailShowing(true) + // should not influence spacing + given(taskbarVisible = false, + navigationMode = BUTTONS_NAVIGATION, + insets = emptyInsets()) + then(expectedContainerPadding = 0) + } + + @Test + fun testDetailShowingInSplitShade_newFooter() { + notificationsQSContainerController.splitShadeEnabled = true + useNewFooter(true) + given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = windowInsets().withStableBottom()) @@ -246,7 +414,13 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { verify(notificationsQSContainer) .setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding)) verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin) - verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding) + val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER) + if (newFooter) { + verify(notificationsQSContainer) + .setQSContainerPaddingBottom(expectedNotificationsMargin) + } else { + verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding) + } Mockito.clearInvocations(notificationsQSContainer) } @@ -263,4 +437,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM) return this } + + private fun useNewFooter(useNewFooter: Boolean) { + whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(useNewFooter) + } }
\ No newline at end of file diff --git a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java index f1d98f09aba3..0509e0cf5ccc 100644 --- a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java +++ b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java @@ -36,7 +36,6 @@ import com.android.server.LocalServices; * will be killed if association/role are revoked. */ public class AssociationCleanUpService extends JobService { - private static final String TAG = LOG_TAG + ".AssociationCleanUpService"; private static final int JOB_ID = AssociationCleanUpService.class.hashCode(); private static final long ONE_DAY_INTERVAL = 3 * 24 * 60 * 60 * 1000; // 1 Day private CompanionDeviceManagerServiceInternal mCdmServiceInternal = LocalServices.getService( @@ -56,7 +55,7 @@ public class AssociationCleanUpService extends JobService { @Override public boolean onStopJob(final JobParameters params) { - Slog.d(TAG, "Association cleanup job stopped; id=" + params.getJobId() + Slog.i(LOG_TAG, "Association cleanup job stopped; id=" + params.getJobId() + ", reason=" + JobParameters.getInternalReasonCodeDescription( params.getInternalStopReasonCode())); diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java index 3ccabaaea2fa..21a677b8383c 100644 --- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java +++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java @@ -18,6 +18,7 @@ package com.android.server.companion; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.companion.AssociationInfo; import android.net.MacAddress; @@ -52,9 +53,10 @@ import java.util.StringJoiner; * Other system component (both inside and outside if the com.android.server.companion package) * should use public {@link AssociationStore} interface. */ +@SuppressLint("LongLogTag") class AssociationStoreImpl implements AssociationStore { private static final boolean DEBUG = false; - private static final String TAG = "AssociationStore"; + private static final String TAG = "CompanionDevice_AssociationStore"; private final Object mLock = new Object(); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index f2e660779e9e..fd130852a43a 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -16,16 +16,17 @@ package com.android.server.companion; -import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; - import android.companion.AssociationInfo; +import android.os.ShellCommand; import android.util.Log; import android.util.Slog; import java.io.PrintWriter; import java.util.List; -class CompanionDeviceShellCommand extends android.os.ShellCommand { +class CompanionDeviceShellCommand extends ShellCommand { + private static final String TAG = "CompanionDevice_ShellCommand"; + private final CompanionDeviceManagerService mService; private final AssociationStore mAssociationStore; @@ -84,7 +85,7 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand { } return 0; } catch (Throwable t) { - Slog.e(LOG_TAG, "Error running a command: $ " + cmd, t); + Slog.e(TAG, "Error running a command: $ " + cmd, t); getErrPrintWriter().println(Log.getStackTraceString(t)); return 1; } diff --git a/services/companion/java/com/android/server/companion/DataStoreUtils.java b/services/companion/java/com/android/server/companion/DataStoreUtils.java index 6055a81c7ca7..8ac741a44ee5 100644 --- a/services/companion/java/com/android/server/companion/DataStoreUtils.java +++ b/services/companion/java/com/android/server/companion/DataStoreUtils.java @@ -25,7 +25,7 @@ import android.os.Environment; import android.util.AtomicFile; import android.util.Slog; -import com.android.internal.util.FunctionalUtils; +import com.android.internal.util.FunctionalUtils.ThrowingConsumer; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -34,8 +34,7 @@ import java.io.File; import java.io.FileOutputStream; final class DataStoreUtils { - - private static final String LOG_TAG = DataStoreUtils.class.getSimpleName(); + private static final String TAG = "CompanionDevice_DataStoreUtils"; static boolean isStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag) throws XmlPullParserException { @@ -71,12 +70,12 @@ final class DataStoreUtils { * Writing to file could fail, for example, if the user has been recently removed and so was * their DE (/data/system_de/<user-id>/) directory. */ - static void writeToFileSafely(@NonNull AtomicFile file, - @NonNull FunctionalUtils.ThrowingConsumer<FileOutputStream> consumer) { + static void writeToFileSafely( + @NonNull AtomicFile file, @NonNull ThrowingConsumer<FileOutputStream> consumer) { try { file.write(consumer); } catch (Exception e) { - Slog.e(LOG_TAG, "Error while writing to file " + file, e); + Slog.e(TAG, "Error while writing to file " + file, e); } } diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java index da33b4446840..d0cc12286b12 100644 --- a/services/companion/java/com/android/server/companion/PersistentDataStore.java +++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java @@ -32,6 +32,7 @@ import static com.android.server.companion.DataStoreUtils.writeToFileSafely; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.companion.AssociationInfo; import android.content.pm.UserInfo; @@ -39,6 +40,7 @@ import android.net.MacAddress; import android.os.Environment; import android.util.ArrayMap; import android.util.AtomicFile; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.TypedXmlPullParser; @@ -146,8 +148,9 @@ import java.util.concurrent.ConcurrentMap; * </state> * }</pre> */ +@SuppressLint("LongLogTag") final class PersistentDataStore { - private static final String LOG_TAG = CompanionDeviceManagerService.LOG_TAG + ".DataStore"; + private static final String TAG = "CompanionDevice_PersistentDataStore"; private static final boolean DEBUG = CompanionDeviceManagerService.DEBUG; private static final int CURRENT_PERSISTENCE_VERSION = 1; @@ -208,10 +211,9 @@ final class PersistentDataStore { void readStateForUser(@UserIdInt int userId, @NonNull Collection<AssociationInfo> associationsOut, @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) { - Slog.i(LOG_TAG, "Reading associations for user " + userId + " from disk"); - + Slog.i(TAG, "Reading associations for user " + userId + " from disk"); final AtomicFile file = getStorageFileForUser(userId); - if (DEBUG) Slog.d(LOG_TAG, " > File=" + file.getBaseFile().getPath()); + if (DEBUG) Log.d(TAG, " > File=" + file.getBaseFile().getPath()); // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize // accesses to the file on the file system using this AtomicFile object. @@ -220,12 +222,12 @@ final class PersistentDataStore { final AtomicFile readFrom; final String rootTag; if (!file.getBaseFile().exists()) { - if (DEBUG) Slog.d(LOG_TAG, " > File does not exist -> Try to read legacy file"); + if (DEBUG) Log.d(TAG, " > File does not exist -> Try to read legacy file"); legacyBaseFile = getBaseLegacyStorageFileForUser(userId); - if (DEBUG) Slog.d(LOG_TAG, " > Legacy file=" + legacyBaseFile.getPath()); + if (DEBUG) Log.d(TAG, " > Legacy file=" + legacyBaseFile.getPath()); if (!legacyBaseFile.exists()) { - if (DEBUG) Slog.d(LOG_TAG, " > Legacy file does not exist -> Abort"); + if (DEBUG) Log.d(TAG, " > Legacy file does not exist -> Abort"); return; } @@ -236,13 +238,13 @@ final class PersistentDataStore { rootTag = XML_TAG_STATE; } - if (DEBUG) Slog.d(LOG_TAG, " > Reading associations..."); + if (DEBUG) Log.d(TAG, " > Reading associations..."); final int version = readStateFromFileLocked(userId, readFrom, rootTag, associationsOut, previouslyUsedIdsPerPackageOut); if (DEBUG) { - Slog.d(LOG_TAG, " > Done reading: " + associationsOut); + Log.d(TAG, " > Done reading: " + associationsOut); if (version < CURRENT_PERSISTENCE_VERSION) { - Slog.d(LOG_TAG, " > File used old format: v." + version + " -> Re-write"); + Log.d(TAG, " > File used old format: v." + version + " -> Re-write"); } } @@ -250,13 +252,13 @@ final class PersistentDataStore { // The data is either in the legacy file or in the legacy format, or both. // Save the data to right file in using the current format. if (DEBUG) { - Slog.d(LOG_TAG, " > Writing the data to " + file.getBaseFile().getPath()); + Log.d(TAG, " > Writing the data to " + file.getBaseFile().getPath()); } persistStateToFileLocked(file, associationsOut, previouslyUsedIdsPerPackageOut); if (legacyBaseFile != null) { // We saved the data to the right file, can delete the old file now. - if (DEBUG) Slog.d(LOG_TAG, " > Deleting legacy file"); + if (DEBUG) Log.d(TAG, " > Deleting legacy file"); legacyBaseFile.delete(); } } @@ -273,11 +275,11 @@ final class PersistentDataStore { void persistStateForUser(@UserIdInt int userId, @NonNull Collection<AssociationInfo> associations, @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) { - Slog.i(LOG_TAG, "Writing associations for user " + userId + " to disk"); - if (DEBUG) Slog.d(LOG_TAG, " > " + associations); + Slog.i(TAG, "Writing associations for user " + userId + " to disk"); + if (DEBUG) Slog.d(TAG, " > " + associations); final AtomicFile file = getStorageFileForUser(userId); - if (DEBUG) Slog.d(LOG_TAG, " > File=" + file.getBaseFile().getPath()); + if (DEBUG) Log.d(TAG, " > File=" + file.getBaseFile().getPath()); // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize // accesses to the file on the file system using this AtomicFile object. synchronized (file) { @@ -312,7 +314,7 @@ final class PersistentDataStore { } return version; } catch (XmlPullParserException | IOException e) { - Slog.e(LOG_TAG, "Error while reading associations file", e); + Slog.e(TAG, "Error while reading associations file", e); return -1; } } @@ -528,7 +530,7 @@ final class PersistentDataStore { associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress, displayName, profile, selfManaged, notify, timeApproved, lastTimeConnected); } catch (Exception e) { - if (DEBUG) Slog.w(LOG_TAG, "Could not create AssociationInfo", e); + if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e); } return associationInfo; } diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java index 76340fc1d6a8..904283f4e60e 100644 --- a/services/companion/java/com/android/server/companion/RolesUtils.java +++ b/services/companion/java/com/android/server/companion/RolesUtils.java @@ -19,20 +19,23 @@ package com.android.server.companion; import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP; import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; -import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.role.RoleManager; import android.companion.AssociationInfo; import android.content.Context; import android.os.UserHandle; +import android.util.Log; import android.util.Slog; import java.util.List; /** Utility methods for accessing {@link RoleManager} APIs. */ +@SuppressLint("LongLogTag") final class RolesUtils { + private static final String TAG = CompanionDeviceManagerService.LOG_TAG; static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName, @NonNull String role) { @@ -45,7 +48,7 @@ final class RolesUtils { static void addRoleHolderForAssociation( @NonNull Context context, @NonNull AssociationInfo associationInfo) { if (DEBUG) { - Slog.d(LOG_TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo); + Log.d(TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo); } final String deviceProfile = associationInfo.getDeviceProfile(); @@ -61,7 +64,7 @@ final class RolesUtils { MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(), success -> { if (!success) { - Slog.e(LOG_TAG, "Failed to add u" + userId + "\\" + packageName + Slog.e(TAG, "Failed to add u" + userId + "\\" + packageName + " to the list of " + deviceProfile + " holders."); } }); @@ -70,7 +73,7 @@ final class RolesUtils { static void removeRoleHolderForAssociation( @NonNull Context context, @NonNull AssociationInfo associationInfo) { if (DEBUG) { - Slog.d(LOG_TAG, "removeRoleHolderForAssociation() associationInfo=" + associationInfo); + Log.d(TAG, "removeRoleHolderForAssociation() associationInfo=" + associationInfo); } final String deviceProfile = associationInfo.getDeviceProfile(); @@ -86,7 +89,7 @@ final class RolesUtils { MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(), success -> { if (!success) { - Slog.e(LOG_TAG, "Failed to remove u" + userId + "\\" + packageName + Slog.e(TAG, "Failed to remove u" + userId + "\\" + packageName + " from the list of " + deviceProfile + " holders."); } }); diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java index 627b0bebc905..a771e7b1aa4a 100644 --- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java +++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java @@ -33,6 +33,7 @@ import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH; import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST; import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER; +import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG; import static com.android.server.companion.presence.Utils.btDeviceToString; import static java.util.Objects.requireNonNull; @@ -70,7 +71,6 @@ import java.util.Set; @SuppressLint("LongLogTag") class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { - private static final boolean DEBUG = false; private static final String TAG = "CompanionDevice_PresenceMonitor_BLE"; /** diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java index 93cbe973b00e..1ba198ae26b0 100644 --- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java +++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java @@ -16,6 +16,7 @@ package com.android.server.companion.presence; +import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG; import static com.android.server.companion.presence.Utils.btDeviceToString; import android.annotation.NonNull; @@ -39,7 +40,6 @@ import java.util.Map; class BluetoothCompanionDeviceConnectionListener extends BluetoothAdapter.BluetoothConnectionCallback implements AssociationStore.OnChangeListener { - private static final boolean DEBUG = false; private static final String TAG = "CompanionDevice_PresenceMonitor_BT"; interface Callback { diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java new file mode 100644 index 000000000000..6371b25e6347 --- /dev/null +++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.presence; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothAdapter; +import android.companion.AssociationInfo; +import android.content.Context; +import android.util.Log; + +import com.android.server.companion.AssociationStore; + +import java.util.HashSet; +import java.util.Set; + +/** + * Class responsible for monitoring companion devices' "presence" status (i.e. + * connected/disconnected for Bluetooth devices; nearby or not for BLE devices). + * + * <p> + * Should only be used by + * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService} + * to which it provides the following API: + * <ul> + * <li> {@link #onSelfManagedDeviceConnected(int)} + * <li> {@link #onSelfManagedDeviceDisconnected(int)} + * <li> {@link #isDevicePresent(int)} + * <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)} + * <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)} + * </ul> + */ +@SuppressLint("LongLogTag") +public class CompanionDevicePresenceMonitor implements AssociationStore.OnChangeListener, + BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback { + static final boolean DEBUG = false; + private static final String TAG = "CompanionDevice_PresenceMonitor"; + + /** Callback for notifying about changes to status of companion devices. */ + public interface Callback { + /** Invoked when companion device is found nearby or connects. */ + void onDeviceAppeared(int associationId); + + /** Invoked when a companion device no longer seen nearby or disconnects. */ + void onDeviceDisappeared(int associationId); + } + + private final @NonNull AssociationStore mAssociationStore; + private final @NonNull Callback mCallback; + private final @NonNull BluetoothCompanionDeviceConnectionListener mBtConnectionListener; + private final @NonNull BleCompanionDeviceScanner mBleScanner; + + // 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 + // companion applications, while at the same be connected via BT, or detected nearby by BLE + // scanner) + private final @NonNull Set<Integer> mConnectedBtDevices = new HashSet<>(); + private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>(); + private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>(); + + public CompanionDevicePresenceMonitor(@NonNull AssociationStore associationStore, + @NonNull Callback callback) { + mAssociationStore = associationStore; + mCallback = callback; + + mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(associationStore, + /* BluetoothCompanionDeviceConnectionListener.Callback */ this); + mBleScanner = new BleCompanionDeviceScanner(associationStore, + /* BleCompanionDeviceScanner.Callback */ this); + } + + /** Initialize {@link CompanionDevicePresenceMonitor} */ + public void init(Context context) { + if (DEBUG) Log.i(TAG, "init()"); + + final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + if (btAdapter != null) { + mBtConnectionListener.init(btAdapter); + mBleScanner.init(context, btAdapter); + } else { + Log.w(TAG, "BluetoothAdapter is NOT available."); + } + + mAssociationStore.registerListener(this); + } + + /** + * @return whether the associated companion devices is present. I.e. device is nearby (for BLE); + * or devices is connected (for Bluetooth); or reported (by the application) to be + * nearby (for "self-managed" associations). + */ + public boolean isDevicePresent(int associationId) { + return mReportedSelfManagedDevices.contains(associationId) + || mConnectedBtDevices.contains(associationId) + || mNearbyBleDevices.contains(associationId); + } + + /** + * Marks a "self-managed" device as connected. + * + * <p> + * Must ONLY be invoked by the + * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService} + * when an application invokes + * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()} + */ + public void onSelfManagedDeviceConnected(int associationId) { + onDevicePresent(mReportedSelfManagedDevices, associationId, "application-reported"); + } + + /** + * Marks a "self-managed" device as disconnected. + * + * <p> + * Must ONLY be invoked by the + * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService} + * when an application invokes + * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()} + */ + public void onSelfManagedDeviceDisconnected(int associationId) { + onDeviceGone(mReportedSelfManagedDevices, associationId, "application-reported"); + } + + @Override + public void onBluetoothCompanionDeviceConnected(int associationId) { + onDevicePresent(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt"); + } + + @Override + public void onBluetoothCompanionDeviceDisconnected(int associationId) { + onDeviceGone(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt"); + } + + @Override + public void onBleCompanionDeviceFound(int associationId) { + onDevicePresent(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble"); + } + + @Override + public void onBleCompanionDeviceLost(int associationId) { + onDeviceGone(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble"); + } + + private void onDevicePresent(@NonNull Set<Integer> presentDevicesForSource, + int newDeviceAssociationId, @NonNull String sourceLoggingTag) { + if (DEBUG) { + Log.i(TAG, "onDevice_Present() id=" + newDeviceAssociationId + + ", source=" + sourceLoggingTag); + Log.d(TAG, " > association=" + + mAssociationStore.getAssociationById(newDeviceAssociationId)); + } + + final boolean alreadyPresent = isDevicePresent(newDeviceAssociationId); + if (DEBUG && alreadyPresent) Log.i(TAG, "Device is already present."); + + final boolean added = presentDevicesForSource.add(newDeviceAssociationId); + if (DEBUG && !added) { + Log.w(TAG, "Association with id " + newDeviceAssociationId + " is ALREADY reported as " + + "present by this source (" + sourceLoggingTag + ")"); + } + + if (alreadyPresent) return; + + mCallback.onDeviceAppeared(newDeviceAssociationId); + } + + private void onDeviceGone(@NonNull Set<Integer> presentDevicesForSource, + int goneDeviceAssociationId, @NonNull String sourceLoggingTag) { + if (DEBUG) { + Log.i(TAG, "onDevice_Gone() id=" + goneDeviceAssociationId + + ", source=" + sourceLoggingTag); + Log.d(TAG, " > association=" + + mAssociationStore.getAssociationById(goneDeviceAssociationId)); + } + + final boolean removed = presentDevicesForSource.remove(goneDeviceAssociationId); + if (!removed) { + if (DEBUG) { + Log.w(TAG, "Association with id " + goneDeviceAssociationId + " was NOT reported " + + "as present by this source (" + sourceLoggingTag + ")"); + } + return; + } + + final boolean stillPresent = isDevicePresent(goneDeviceAssociationId); + if (stillPresent) { + if (DEBUG) Log.i(TAG, " Device is still present."); + return; + } + + mCallback.onDeviceDisappeared(goneDeviceAssociationId); + } + + /** + * Implements + * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)} + */ + @Override + public void onAssociationRemoved(@NonNull AssociationInfo association) { + final int id = association.getId(); + if (DEBUG) { + Log.i(TAG, "onAssociationRemoved() id=" + id); + Log.d(TAG, " > association=" + association); + } + + mConnectedBtDevices.remove(id); + mNearbyBleDevices.remove(id); + mReportedSelfManagedDevices.remove(id); + + // Do NOT call mCallback.onDeviceDisappeared()! + // CompanionDeviceManagerService will know that the association is removed, and will do + // what's needed. + } +} diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index db510cb6422a..7714dbc55aa9 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -33,6 +33,7 @@ import android.util.PackageUtils; import android.util.Slog; import com.android.internal.os.IBinaryTransparencyService; +import com.android.internal.util.FrameworkStatsLog; import java.io.File; import java.io.FileDescriptor; @@ -373,6 +374,7 @@ public class BinaryTransparencyService extends SystemService { private void getVBMetaDigestInformation() { mVbmetaDigest = SystemProperties.get(SYSPROP_NAME_VBETA_DIGEST, VBMETA_DIGEST_UNAVAILABLE); Slog.d(TAG, String.format("VBMeta Digest: %s", mVbmetaDigest)); + FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest); } @NonNull @@ -437,6 +439,13 @@ public class BinaryTransparencyService extends SystemService { } else { mBinaryHashes.put(packageName, sha256digest); } + + if (packageInfo.isApex) { + FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED, + packageInfo.packageName, + packageInfo.getLongVersionCode(), + mBinaryHashes.get(packageInfo.packageName)); + } } } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "Could not find package with name " + packageName); @@ -466,6 +475,8 @@ public class BinaryTransparencyService extends SystemService { } else { mBinaryHashes.put(packageInfo.packageName, sha256digest); } + FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED, packageInfo.packageName, + packageInfo.getLongVersionCode(), mBinaryHashes.get(packageInfo.packageName)); Slog.d(TAG, String.format("Last update time for %s: %d", packageInfo.packageName, packageInfo.lastUpdateTime)); mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 442b9de9911d..fafe908564ef 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -459,7 +459,7 @@ public class ActivityManagerService extends IActivityManager.Stub * broadcasts */ private static final boolean ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT = - SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", false); + SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", true); static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM; static final String TAG_BACKUP = TAG + POSTFIX_BACKUP; @@ -2901,16 +2901,31 @@ public class ActivityManagerService extends IActivityManager.Stub mActivityTaskManager.setPackageScreenCompatMode(packageName, mode); } - private boolean hasUsageStatsPermission(String callingPackage) { + private boolean hasUsageStatsPermission(String callingPackage, int callingUid, int callingPid) { final int mode = mAppOpsService.noteOperation(AppOpsManager.OP_GET_USAGE_STATS, - Binder.getCallingUid(), callingPackage, null, false, "", false).getOpMode(); + callingUid, callingPackage, null, false, "", false).getOpMode(); if (mode == AppOpsManager.MODE_DEFAULT) { - return checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS) + return checkPermission(Manifest.permission.PACKAGE_USAGE_STATS, callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; } return mode == AppOpsManager.MODE_ALLOWED; } + private boolean hasUsageStatsPermission(String callingPackage) { + return hasUsageStatsPermission(callingPackage, + Binder.getCallingUid(), Binder.getCallingPid()); + } + + private void enforceUsageStatsPermission(String callingPackage, + int callingUid, int callingPid, String operation) { + if (!hasUsageStatsPermission(callingPackage, callingUid, callingPid)) { + final String errorMsg = "Permission denial for <" + operation + "> from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " which requires PACKAGE_USAGE_STATS permission"; + throw new SecurityException(errorMsg); + } + } + @Override public int getPackageProcessState(String packageName, String callingPackage) { if (!hasUsageStatsPermission(callingPackage)) { @@ -12783,7 +12798,7 @@ public class ActivityManagerService extends IActivityManager.Stub noAction.add(null); actions = noAction.iterator(); } - boolean onlyProtectedBroadcasts = actions.hasNext(); + boolean onlyProtectedBroadcasts = true; // Collect stickies of users and check if broadcast is only registered for protected // broadcasts @@ -12857,6 +12872,8 @@ public class ActivityManagerService extends IActivityManager.Stub // Change is not enabled, thus not targeting T+. Assume exported. flags |= Context.RECEIVER_EXPORTED; } + } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) { + flags |= Context.RECEIVER_EXPORTED; } } @@ -13349,6 +13366,13 @@ public class ActivityManagerService extends IActivityManager.Stub backgroundActivityStartsToken = null; } } + + // TODO (206518114): We need to use the "real" package name which sent the broadcast, + // in case the broadcast is sent via PendingIntent. + if (brOptions.getIdForResponseEvent() > 0) { + enforceUsageStatsPermission(callerPackage, realCallingUid, realCallingPid, + "recordResponseEventWhileInBackground()"); + } } // Verify that protected broadcasts are only being sent by system code, diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 1315293abaa4..465623f800c5 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -54,6 +54,7 @@ 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_DISABLED_UNTIL_USED_COMPONENTS; import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE; +import static android.os.PowerExemptionManager.REASON_CARRIER_PRIVILEGED_APP; import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER; import static android.os.PowerExemptionManager.REASON_DENIED; import static android.os.PowerExemptionManager.REASON_DEVICE_DEMO_MODE; @@ -65,6 +66,7 @@ import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI; import static android.os.PowerExemptionManager.REASON_PROFILE_OWNER; import static android.os.PowerExemptionManager.REASON_ROLE_DIALER; import static android.os.PowerExemptionManager.REASON_ROLE_EMERGENCY; +import static android.os.PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED; import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE; import static android.os.PowerExemptionManager.REASON_SYSTEM_UID; import static android.os.Process.SYSTEM_UID; @@ -125,6 +127,7 @@ import android.provider.DeviceConfig.OnPropertiesChangedListener; import android.provider.DeviceConfig.Properties; import android.provider.Settings; import android.provider.Settings.Global; +import android.telephony.TelephonyManager; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; @@ -138,6 +141,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.TriConsumer; import com.android.server.AppStateTracker; import com.android.server.LocalServices; +import com.android.server.SystemConfig; import com.android.server.apphibernation.AppHibernationManagerInternal; import com.android.server.pm.UserManagerInternal; import com.android.server.usage.AppStandbyInternal; @@ -229,6 +233,24 @@ public final class AppRestrictionController { @GuardedBy("mLock") private final HashMap<String, Boolean> mSystemModulesCache = new HashMap<>(); + /** + * The pre-config packages that are exempted from the background restrictions. + */ + private ArraySet<String> mBgRestrictionExemptioFromSysConfig; + + /** + * Lock specifically for bookkeeping around the carrier-privileged app set. + * Do not acquire any other locks while holding this one. Methods that + * require this lock to be held are named with a "CPL" suffix. + */ + private final Object mCarrierPrivilegedLock = new Object(); + + /** + * List of carrier-privileged apps that should be excluded from standby. + */ + @GuardedBy("mCarrierPrivilegedLock") + private List<String> mCarrierPrivilegedApps; + final ActivityManagerService mActivityManagerService; /** @@ -690,6 +712,7 @@ public final class AppRestrictionController { DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, ActivityThread.currentApplication().getMainExecutor(), mConstantsObserver); mConstantsObserver.start(); + initBgRestrictionExemptioFromSysConfig(); initRestrictionStates(); initSystemModuleNames(); registerForUidObservers(); @@ -711,6 +734,22 @@ public final class AppRestrictionController { initRestrictionStates(); } + private void initBgRestrictionExemptioFromSysConfig() { + mBgRestrictionExemptioFromSysConfig = + SystemConfig.getInstance().getBgRestrictionExemption(); + if (DEBUG_BG_RESTRICTION_CONTROLLER) { + final ArraySet<String> exemptedPkgs = mBgRestrictionExemptioFromSysConfig; + for (int i = exemptedPkgs.size() - 1; i >= 0; i--) { + Slog.i(TAG, "bg-restriction-exemption: " + exemptedPkgs.valueAt(i)); + } + } + } + + private boolean isExemptedFromSysConfig(String packageName) { + return mBgRestrictionExemptioFromSysConfig != null + && mBgRestrictionExemptioFromSysConfig.contains(packageName); + } + private void initRestrictionStates() { final int[] allUsers = mInjector.getUserManagerInternal().getUserIds(); for (int userId : allUsers) { @@ -1542,14 +1581,11 @@ public final class AppRestrictionController { } } - boolean isOnDeviceIdleAllowlist(int uid, boolean allowExceptIdle) { + boolean isOnDeviceIdleAllowlist(int uid) { final int appId = UserHandle.getAppId(uid); - final int[] allowlist = allowExceptIdle - ? mDeviceIdleExceptIdleAllowlist - : mDeviceIdleAllowlist; - - return Arrays.binarySearch(allowlist, appId) >= 0; + return Arrays.binarySearch(mDeviceIdleAllowlist, appId) >= 0 + || Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, appId) >= 0; } void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids) { @@ -1570,7 +1606,7 @@ public final class AppRestrictionController { if (UserHandle.isCore(uid)) { return REASON_SYSTEM_UID; } - if (isOnDeviceIdleAllowlist(uid, false)) { + if (isOnDeviceIdleAllowlist(uid)) { return REASON_ALLOWLISTED_PACKAGE; } final ActivityManagerInternal am = mInjector.getActivityManagerInternal(); @@ -1604,6 +1640,10 @@ public final class AppRestrictionController { return REASON_OP_ACTIVATE_PLATFORM_VPN; } else if (isSystemModule(pkg)) { return REASON_SYSTEM_MODULE; + } else if (isCarrierApp(pkg)) { + return REASON_CARRIER_PRIVILEGED_APP; + } else if (isExemptedFromSysConfig(pkg)) { + return REASON_SYSTEM_ALLOW_LISTED; } } } @@ -1616,6 +1656,37 @@ public final class AppRestrictionController { return REASON_DENIED; } + private boolean isCarrierApp(String packageName) { + synchronized (mCarrierPrivilegedLock) { + if (mCarrierPrivilegedApps == null) { + fetchCarrierPrivilegedAppsCPL(); + } + if (mCarrierPrivilegedApps != null) { + return mCarrierPrivilegedApps.contains(packageName); + } + return false; + } + } + + private void clearCarrierPrivilegedApps() { + if (DEBUG_BG_RESTRICTION_CONTROLLER) { + Slog.i(TAG, "Clearing carrier privileged apps list"); + } + synchronized (mCarrierPrivilegedLock) { + mCarrierPrivilegedApps = null; // Need to be refetched. + } + } + + @GuardedBy("mCarrierPrivilegedLock") + private void fetchCarrierPrivilegedAppsCPL() { + final TelephonyManager telephonyManager = mInjector.getTelephonyManager(); + mCarrierPrivilegedApps = + telephonyManager.getCarrierPrivilegedPackagesForAllActiveSubscriptions(); + if (DEBUG_BG_RESTRICTION_CONTROLLER) { + Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps); + } + } + private boolean isRoleHeldByUid(@NonNull String roleName, int uid) { synchronized (mLock) { final ArrayList<String> roles = mUidRolesMapping.get(uid); @@ -1791,6 +1862,7 @@ public final class AppRestrictionController { private AppBatteryExemptionTracker mAppBatteryExemptionTracker; private AppFGSTracker mAppFGSTracker; private AppMediaSessionTracker mAppMediaSessionTracker; + private TelephonyManager mTelephonyManager; Injector(Context context) { mContext = context; @@ -1890,6 +1962,13 @@ public final class AppRestrictionController { return mRoleManager; } + TelephonyManager getTelephonyManager() { + if (mTelephonyManager == null) { + mTelephonyManager = getContext().getSystemService(TelephonyManager.class); + } + return mTelephonyManager; + } + AppFGSTracker getAppFGSTracker() { return mAppFGSTracker; } @@ -1939,6 +2018,19 @@ public final class AppRestrictionController { onUidAdded(uid); } } + } + // fall through. + case Intent.ACTION_PACKAGE_CHANGED: { + final String pkgName = intent.getData().getSchemeSpecificPart(); + final String[] cmpList = intent.getStringArrayExtra( + Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); + // If this is PACKAGE_ADDED (cmpList == null), or if it's a whole-package + // enable/disable event (cmpList is just the package name itself), drop + // our carrier privileged app & system-app caches and let them refresh + if (cmpList == null + || (cmpList.length == 1 && pkgName.equals(cmpList[0]))) { + clearCarrierPrivilegedApps(); + } } break; case Intent.ACTION_PACKAGE_FULLY_REMOVED: { final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); @@ -1986,6 +2078,7 @@ public final class AppRestrictionController { }; final IntentFilter packageFilter = new IntentFilter(); packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); packageFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); packageFilter.addDataScheme("package"); mContext.registerReceiverForAllUsers(broadcastReceiver, packageFilter, null, mBgHandler); diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index e2921e9f5e71..a83fdd0e74cd 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -56,6 +56,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.PowerExemptionManager; import android.os.PowerExemptionManager.ReasonCode; import android.os.PowerExemptionManager.TempAllowListType; import android.os.Process; @@ -870,7 +871,7 @@ public final class BroadcastQueue { + " due to receiver " + filter.receiverList.app + " (uid " + filter.receiverList.uid + ")" + " not specifying RECEIVER_EXPORTED"); - // skip = true; + skip = true; } if (skip) { @@ -1857,22 +1858,36 @@ public final class BroadcastQueue { } private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) { - final String targetPackage = getTargetPackage(r); - // Ignore non-explicit broadcasts - if (targetPackage == null) { - return; - } // TODO (206518114): Only allow apps with ACCESS_PACKAGE_USAGE_STATS to set // getIdForResponseEvent. + // TODO (217251579): Temporarily use temp-allowlist reason to identify + // push messages and record response events. + useTemporaryAllowlistReasonAsSignal(r); if (r.options == null || r.options.getIdForResponseEvent() <= 0) { return; } + final String targetPackage = getTargetPackage(r); + // Ignore non-explicit broadcasts + if (targetPackage == null) { + return; + } getUsageStatsManagerInternal().reportBroadcastDispatched( r.callingUid, targetPackage, UserHandle.of(r.userId), r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(), mService.getUidStateLocked(targetUid)); } + private void useTemporaryAllowlistReasonAsSignal(BroadcastRecord r) { + if (r.options == null || r.options.getIdForResponseEvent() > 0) { + return; + } + final int reasonCode = r.options.getTemporaryAppAllowlistReasonCode(); + if (reasonCode == PowerExemptionManager.REASON_PUSH_MESSAGING + || reasonCode == PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA) { + r.options.recordResponseEventWhileInBackground(reasonCode); + } + } + @NonNull private UsageStatsManagerInternal getUsageStatsManagerInternal() { final UsageStatsManagerInternal usageStatsManagerInternal = diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 65be5f06d08f..5330845907ca 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -29,7 +29,7 @@ import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.AudioRoutesInfo; import android.media.AudioSystem; -import android.media.BtProfileConnectionInfo; +import android.media.BluetoothProfileConnectionInfo; import android.media.IAudioRoutesObserver; import android.media.ICapturePresetDevicesRoleDispatcher; import android.media.ICommunicationDeviceDispatcher; @@ -531,12 +531,12 @@ import java.util.concurrent.atomic.AtomicBoolean; /*package*/ static final class BtDeviceChangedData { final @Nullable BluetoothDevice mNewDevice; final @Nullable BluetoothDevice mPreviousDevice; - final @NonNull BtProfileConnectionInfo mInfo; + final @NonNull BluetoothProfileConnectionInfo mInfo; final @NonNull String mEventSource; BtDeviceChangedData(@Nullable BluetoothDevice newDevice, @Nullable BluetoothDevice previousDevice, - @NonNull BtProfileConnectionInfo info, @NonNull String eventSource) { + @NonNull BluetoothProfileConnectionInfo info, @NonNull String eventSource) { mNewDevice = newDevice; mPreviousDevice = previousDevice; mInfo = info; @@ -568,9 +568,9 @@ import java.util.concurrent.atomic.AtomicBoolean; mDevice = device; mState = state; mProfile = d.mInfo.getProfile(); - mSupprNoisy = d.mInfo.getSuppressNoisyIntent(); + mSupprNoisy = d.mInfo.isSuppressNoisyIntent(); mVolume = d.mInfo.getVolume(); - mIsLeOutput = d.mInfo.getIsLeOutput(); + mIsLeOutput = d.mInfo.isLeOutput(); mEventSource = d.mEventSource; mAudioSystemDevice = audioDevice; mMusicDevice = AudioSystem.DEVICE_NONE; @@ -641,7 +641,7 @@ import java.util.concurrent.atomic.AtomicBoolean; audioDevice = AudioSystem.DEVICE_OUT_HEARING_AID; break; case BluetoothProfile.LE_AUDIO: - if (d.mInfo.getIsLeOutput()) { + if (d.mInfo.isLeOutput()) { audioDevice = AudioSystem.DEVICE_OUT_BLE_HEADSET; } else { audioDevice = AudioSystem.DEVICE_IN_BLE_HEADSET; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 407d42ea510d..4494d963418e 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -85,7 +85,7 @@ import android.media.AudioPlaybackConfiguration; import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; import android.media.AudioSystem; -import android.media.BtProfileConnectionInfo; +import android.media.BluetoothProfileConnectionInfo; import android.media.IAudioFocusDispatcher; import android.media.IAudioModeDispatcher; import android.media.IAudioRoutesObserver; @@ -6434,14 +6434,14 @@ public class AudioService extends IAudioService.Stub * See AudioManager.handleBluetoothActiveDeviceChanged(...) */ public void handleBluetoothActiveDeviceChanged(BluetoothDevice newDevice, - BluetoothDevice previousDevice, @NonNull BtProfileConnectionInfo info) { + BluetoothDevice previousDevice, @NonNull BluetoothProfileConnectionInfo info) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BLUETOOTH_STACK) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Bluetooth is the only caller allowed"); } if (info == null) { - throw new IllegalArgumentException("Illegal null BtProfileConnectionInfo for device " - + previousDevice + " -> " + newDevice); + throw new IllegalArgumentException("Illegal null BluetoothProfileConnectionInfo for" + + " device " + previousDevice + " -> " + newDevice); } final int profile = info.getProfile(); if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 47f31d505867..8e4674c01b2d 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -31,7 +31,7 @@ import android.content.Intent; import android.media.AudioDeviceAttributes; import android.media.AudioManager; import android.media.AudioSystem; -import android.media.BtProfileConnectionInfo; +import android.media.BluetoothProfileConnectionInfo; import android.os.Binder; import android.os.UserHandle; import android.provider.Settings; @@ -489,11 +489,13 @@ public class BtHelper { if (proxy.getConnectionState(btDevice) == BluetoothProfile.STATE_CONNECTED) { mDeviceBroker.queueOnBluetoothActiveDeviceChanged( new AudioDeviceBroker.BtDeviceChangedData(btDevice, null, - new BtProfileConnectionInfo(profile), "mBluetoothProfileServiceListener")); + new BluetoothProfileConnectionInfo(profile), + "mBluetoothProfileServiceListener")); } else { mDeviceBroker.queueOnBluetoothActiveDeviceChanged( new AudioDeviceBroker.BtDeviceChangedData(null, btDevice, - new BtProfileConnectionInfo(profile), "mBluetoothProfileServiceListener")); + new BluetoothProfileConnectionInfo(profile), + "mBluetoothProfileServiceListener")); } } diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index d0ce9ef47c35..5de162ce711c 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -222,6 +222,22 @@ abstract class DisplayDevice { } /** + * Returns the system preferred display mode. + */ + public Display.Mode getSystemPreferredDisplayModeLocked() { + return EMPTY_DISPLAY_MODE; + } + + /** + * Returns the display mode that was being used when this display was first found by + * display manager. + * @hide + */ + public Display.Mode getActiveDisplayModeAtStartLocked() { + return EMPTY_DISPLAY_MODE; + } + + /** * Sets the requested color mode. */ public void setRequestedColorModeLocked(int colorMode) { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 4e88acd11fac..7f1482e6dde1 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1833,6 +1833,16 @@ public final class DisplayManagerService extends SystemService { } } + Display.Mode getSystemPreferredDisplayModeInternal(int displayId) { + synchronized (mSyncRoot) { + final DisplayDevice device = getDeviceForDisplayLocked(displayId); + if (device == null) { + return null; + } + return device.getSystemPreferredDisplayModeLocked(); + } + } + void setShouldAlwaysRespectAppRequestedModeInternal(boolean enabled) { mDisplayModeDirector.setShouldAlwaysRespectAppRequestedMode(enabled); } @@ -2183,6 +2193,16 @@ public final class DisplayManagerService extends SystemService { } } + Display.Mode getActiveDisplayModeAtStart(int displayId) { + synchronized (mSyncRoot) { + final DisplayDevice device = getDeviceForDisplayLocked(displayId); + if (device == null) { + return null; + } + return device.getActiveDisplayModeAtStartLocked(); + } + } + void setAmbientColorTemperatureOverride(float cct) { synchronized (mSyncRoot) { final DisplayPowerController displayPowerController = mDisplayPowerControllers.get( @@ -3471,6 +3491,16 @@ public final class DisplayManagerService extends SystemService { } @Override // Binder call + public Display.Mode getSystemPreferredDisplayMode(int displayId) { + final long token = Binder.clearCallingIdentity(); + try { + return getSystemPreferredDisplayModeInternal(displayId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call public void setShouldAlwaysRespectAppRequestedMode(boolean enabled) { mContext.enforceCallingOrSelfPermission( Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS, diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index a9875c873bbb..bfdac5781b75 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -68,6 +68,8 @@ class DisplayManagerShellCommand extends ShellCommand { return clearUserPreferredDisplayMode(); case "get-user-preferred-display-mode": return getUserPreferredDisplayMode(); + case "get-active-display-mode-at-start": + return getActiveDisplayModeAtStart(); case "set-match-content-frame-rate-pref": return setMatchContentFrameRateUserPreference(); case "get-match-content-frame-rate-pref": @@ -125,6 +127,9 @@ class DisplayManagerShellCommand extends ShellCommand { pw.println(" Returns the user preferred display mode or null if no mode is set by user." + "If DISPLAY_ID is passed, the mode for display with id = DISPLAY_ID is " + "returned, else global display mode is returned."); + pw.println(" get-active-display-mode-at-start DISPLAY_ID"); + pw.println(" Returns the display mode which was found at boot time of display with " + + "id = DISPLAY_ID"); pw.println(" set-match-content-frame-rate-pref PREFERENCE"); pw.println(" Sets the match content frame rate preference as PREFERENCE "); pw.println(" get-match-content-frame-rate-pref"); @@ -298,6 +303,30 @@ class DisplayManagerShellCommand extends ShellCommand { return 0; } + private int getActiveDisplayModeAtStart() { + final String displayIdText = getNextArg(); + if (displayIdText == null) { + getErrPrintWriter().println("Error: no displayId specified"); + return 1; + } + final int displayId; + try { + displayId = Integer.parseInt(displayIdText); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: invalid displayId"); + return 1; + } + + Display.Mode mode = mService.getActiveDisplayModeAtStart(displayId); + if (mode == null) { + getOutPrintWriter().println("Boot display mode: null"); + return 0; + } + getOutPrintWriter().println("Boot display mode: " + mode.getPhysicalWidth() + " " + + mode.getPhysicalHeight() + " " + mode.getRefreshRate()); + return 0; + } + private int setMatchContentFrameRateUserPreference() { final String matchContentFrameRatePrefText = getNextArg(); if (matchContentFrameRatePrefText == null) { diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 3a9ef0a83f6b..a31c2314bd1f 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -192,8 +192,12 @@ final class LocalDisplayAdapter extends DisplayAdapter { private float mBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT; private float mSdrBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT; private int mDefaultModeId = INVALID_MODE_ID; + private int mSystemPreferredModeId = INVALID_MODE_ID; private int mDefaultModeGroup; private int mUserPreferredModeId = INVALID_MODE_ID; + // This is used only for the purpose of testing, to verify if the mode was correct when the + // device started or booted. + private int mActiveDisplayModeAtStartId = INVALID_MODE_ID; private Display.Mode mUserPreferredMode; private int mActiveModeId = INVALID_MODE_ID; private DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs = @@ -208,7 +212,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { private boolean mSidekickActive; private SidekickInternal mSidekickInternal; private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo; - // The supported display modes according in SurfaceFlinger + // The supported display modes according to SurfaceFlinger private SurfaceControl.DisplayMode[] mSfDisplayModes; // The active display mode in SurfaceFlinger private SurfaceControl.DisplayMode mActiveSfDisplayMode; @@ -230,6 +234,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mBacklightAdapter = new BacklightAdapter(displayToken, isDefaultDisplay, mSurfaceControlProxy); mDisplayDeviceConfig = null; + mActiveDisplayModeAtStartId = dynamicInfo.activeDisplayModeId; } @Override @@ -238,12 +243,23 @@ final class LocalDisplayAdapter extends DisplayAdapter { } /** + * Returns the boot display mode of this display. + * @hide + */ + @Override + public Display.Mode getActiveDisplayModeAtStartLocked() { + return findMode(mActiveDisplayModeAtStartId); + } + + /** * Returns true if there is a change. **/ public boolean updateDisplayPropertiesLocked(SurfaceControl.StaticDisplayInfo staticInfo, SurfaceControl.DynamicDisplayInfo dynamicInfo, SurfaceControl.DesiredDisplayModeSpecs modeSpecs) { - boolean changed = updateDisplayModesLocked( + boolean changed = + updateSystemPreferredDisplayMode(dynamicInfo.preferredBootDisplayMode); + changed |= updateDisplayModesLocked( dynamicInfo.supportedDisplayModes, dynamicInfo.activeDisplayModeId, modeSpecs); changed |= updateStaticInfo(staticInfo); changed |= updateColorModesLocked(dynamicInfo.supportedColorModes, @@ -369,8 +385,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { // For a new display, we need to initialize the default mode ID. if (mDefaultModeId == INVALID_MODE_ID) { - mDefaultModeId = activeRecord.mMode.getModeId(); - mDefaultModeGroup = mActiveSfDisplayMode.group; + mDefaultModeId = mSystemPreferredModeId != INVALID_MODE_ID + ? mSystemPreferredModeId : activeRecord.mMode.getModeId(); + mDefaultModeGroup = mSystemPreferredModeId != INVALID_MODE_ID + ? getModeById(mSfDisplayModes, mSystemPreferredModeId).group + : mActiveSfDisplayMode.group; } else if (modesAdded && activeModeChanged) { Slog.d(TAG, "New display modes are added and the active mode has changed, " + "use active mode as default mode."); @@ -531,6 +550,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { return true; } + private boolean updateSystemPreferredDisplayMode(int modeId) { + if (!mSurfaceControlProxy.getBootDisplayModeSupport() + || mSystemPreferredModeId == modeId) { + return false; + } + mSystemPreferredModeId = modeId; + return true; + } + private SurfaceControl.DisplayMode getModeById(SurfaceControl.DisplayMode[] supportedModes, int modeId) { for (SurfaceControl.DisplayMode mode : supportedModes) { @@ -857,6 +885,16 @@ final class LocalDisplayAdapter extends DisplayAdapter { if (oldModeId != getPreferredModeId()) { updateDeviceInfoLocked(); } + + if (!mSurfaceControlProxy.getBootDisplayModeSupport()) { + return; + } + if (mUserPreferredMode == null) { + mSurfaceControlProxy.clearBootDisplayMode(getDisplayTokenLocked()); + } else { + mSurfaceControlProxy.setBootDisplayMode(getDisplayTokenLocked(), + mUserPreferredMode.getModeId()); + } } @Override @@ -865,6 +903,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { } @Override + public Display.Mode getSystemPreferredDisplayModeLocked() { + return findMode(mSystemPreferredModeId); + } + + @Override public void setRequestedColorModeLocked(int colorMode) { requestColorModeLocked(colorMode); } @@ -1072,6 +1115,17 @@ final class LocalDisplayAdapter extends DisplayAdapter { return matchingModeId; } + // Returns a mode with id = modeId. + private Display.Mode findMode(int modeId) { + for (int i = 0; i < mSupportedModes.size(); i++) { + Display.Mode supportedMode = mSupportedModes.valueAt(i).mMode; + if (supportedMode.getModeId() == modeId) { + return supportedMode; + } + } + return null; + } + // Returns a mode with resolution (width, height) and/or refreshRate. If any one of the // resolution or refresh-rate is valid, a mode having the valid parameters is returned. private Display.Mode findMode(int width, int height, float refreshRate) { @@ -1318,6 +1372,18 @@ final class LocalDisplayAdapter extends DisplayAdapter { return SurfaceControl.setActiveColorMode(displayToken, colorMode); } + public boolean getBootDisplayModeSupport() { + return SurfaceControl.getBootDisplayModeSupport(); + } + + public void setBootDisplayMode(IBinder displayToken, int modeId) { + SurfaceControl.setBootDisplayMode(displayToken, modeId); + } + + public void clearBootDisplayMode(IBinder displayToken) { + SurfaceControl.clearBootDisplayMode(displayToken); + } + public void setAutoLowLatencyMode(IBinder displayToken, boolean on) { SurfaceControl.setAutoLowLatencyMode(displayToken, on); @@ -1340,7 +1406,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { return SurfaceControl.setDisplayBrightness(displayToken, sdrBacklight, sdrNits, displayBacklight, displayNits); } - } static class BacklightAdapter { diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java index 1ebd1f5a535c..d8672fc07619 100644 --- a/services/core/java/com/android/server/display/RampAnimator.java +++ b/services/core/java/com/android/server/display/RampAnimator.java @@ -70,7 +70,7 @@ class RampAnimator<T> { mRate = 0; mTargetValue = target; mCurrentValue = target; - mProperty.setValue(mObject, target); + setPropertyValue(target); if (mAnimating) { mAnimating = false; cancelAnimationCallback(); @@ -125,6 +125,15 @@ class RampAnimator<T> { mListener = listener; } + /** + * Sets the brightness property by converting the given value from HLG space + * into linear space. + */ + private void setPropertyValue(float val) { + final float linearVal = BrightnessUtils.convertGammaToLinear(val); + mProperty.setValue(mObject, linearVal); + } + private void postAnimationCallback() { mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimationCallback, null); } @@ -160,9 +169,7 @@ class RampAnimator<T> { final float oldCurrentValue = mCurrentValue; mCurrentValue = mAnimatedValue; if (oldCurrentValue != mCurrentValue) { - // Convert value from HLG into linear space for the property. - final float linearCurrentVal = BrightnessUtils.convertGammaToLinear(mCurrentValue); - mProperty.setValue(mObject, linearCurrentVal); + setPropertyValue(mCurrentValue); } if (mTargetValue != mCurrentValue) { postAnimationCallback(); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java index 4792821f652b..79820a222dc0 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java @@ -29,6 +29,7 @@ import android.os.Environment; import android.os.SystemProperties; import android.provider.Settings.Global; import android.util.ArrayMap; +import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -246,9 +247,11 @@ public class HdmiCecConfig { mAllowedValues.add(value); if (mContext.getResources().getBoolean(defaultResId)) { if (mDefaultValue != null) { - throw new VerificationException("Invalid CEC setup for '" - + this.getName() + "' setting. " - + "Setting already has a default value."); + Slog.e(TAG, + "Failed to set '" + value + "' as a default for '" + this.getName() + + "': Setting already has a default ('" + mDefaultValue + + "')."); + return; } mDefaultValue = value; } @@ -277,6 +280,11 @@ public class HdmiCecConfig { mContext = context; mStorageAdapter = storageAdapter; + // IMPORTANT: when adding a config value for a particular setting, register that value AFTER + // the existing values for that setting. That way, defaults set in the RRO are forward + // compatible even if the RRO doesn't include that new value yet + // (e.g. because it's ported from a previous release). + Setting hdmiCecEnabled = registerSetting( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, R.bool.config_cecHdmiCecEnabled_userConfigurable); @@ -313,15 +321,15 @@ public class HdmiCecConfig { powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_TV, R.bool.config_cecPowerControlModeTv_allowed, R.bool.config_cecPowerControlModeTv_default); - powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM, - R.bool.config_cecPowerControlModeTvAndAudioSystem_allowed, - R.bool.config_cecPowerControlModeTvAndAudioSystem_default); powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_BROADCAST, R.bool.config_cecPowerControlModeBroadcast_allowed, R.bool.config_cecPowerControlModeBroadcast_default); powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_NONE, R.bool.config_cecPowerControlModeNone_allowed, R.bool.config_cecPowerControlModeNone_default); + powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM, + R.bool.config_cecPowerControlModeTvAndAudioSystem_allowed, + R.bool.config_cecPowerControlModeTvAndAudioSystem_default); Setting powerStateChangeOnActiveSourceLost = registerSetting( HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST, diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 64b4da7c5bba..bfaa7b37fa33 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -150,7 +150,7 @@ public class InputManagerService extends IInputManager.Stub static final String TAG = "InputManager"; static final boolean DEBUG = false; - private static final boolean USE_SPY_WINDOW_GESTURE_MONITORS = false; + private static final boolean USE_SPY_WINDOW_GESTURE_MONITORS = true; private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; private static final String PORT_ASSOCIATIONS_PATH = "etc/input-port-associations.xml"; diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java index 4c2616667a02..9846a2ba48a4 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java @@ -19,7 +19,6 @@ package com.android.server.inputmethod; import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import android.annotation.NonNull; -import android.graphics.Rect; import android.os.Process; import android.view.InputApplicationHandle; import android.view.InputChannel; @@ -33,8 +32,11 @@ final class HandwritingEventReceiverSurface { public static final String TAG = HandwritingEventReceiverSurface.class.getSimpleName(); static final boolean DEBUG = HandwritingModeController.DEBUG; - private final int mClientPid; - private final int mClientUid; + // Place the layer below the highest layer to place it under gesture monitors. If the surface + // is above gesture monitors, then edge-back and swipe-up gestures won't work when this surface + // is intercepting. + // TODO(b/217538817): Specify the ordering in WM by usage. + private static final int HANDWRITING_SURFACE_LAYER = Integer.MAX_VALUE - 1; private final InputApplicationHandle mApplicationHandle; private final InputWindowHandle mWindowHandle; @@ -44,9 +46,6 @@ final class HandwritingEventReceiverSurface { HandwritingEventReceiverSurface(String name, int displayId, @NonNull SurfaceControl sc, @NonNull InputChannel inputChannel) { - // Initialized the window as being owned by the system. - mClientPid = Process.myPid(); - mClientUid = Process.myUid(); mApplicationHandle = new InputApplicationHandle(null, name, DEFAULT_DISPATCHING_TIMEOUT_MILLIS); @@ -57,29 +56,26 @@ final class HandwritingEventReceiverSurface { mWindowHandle.name = name; mWindowHandle.token = mClientChannel.getToken(); mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; - mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; mWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS; mWindowHandle.visible = true; mWindowHandle.focusable = false; mWindowHandle.hasWallpaper = false; mWindowHandle.paused = false; - mWindowHandle.ownerPid = mClientPid; - mWindowHandle.ownerUid = mClientUid; + mWindowHandle.ownerPid = Process.myPid(); + mWindowHandle.ownerUid = Process.myUid(); mWindowHandle.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY | WindowManager.LayoutParams.INPUT_FEATURE_INTERCEPTS_STYLUS; mWindowHandle.scaleFactor = 1.0f; mWindowHandle.trustedOverlay = true; - mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface as crop */); + mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */); final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.setInputWindowInfo(mInputSurface, mWindowHandle); - t.setLayer(mInputSurface, Integer.MAX_VALUE); + t.setLayer(mInputSurface, HANDWRITING_SURFACE_LAYER); t.setPosition(mInputSurface, 0, 0); - // Use an arbitrarily large crop that is positioned at the origin. The crop determines the - // bounds and the coordinate space of the input events, so it must start at the origin to - // receive input in display space. - // TODO(b/210039666): fix this in SurfaceFlinger and avoid the hack. - t.setCrop(mInputSurface, new Rect(0, 0, 10000, 10000)); + t.setCrop(mInputSurface, null /* crop to parent surface */); t.show(mInputSurface); t.apply(); diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 96d7521439cd..70e968fa8bdc 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -201,6 +201,10 @@ public final class NotificationRecord { private boolean mIsAppImportanceLocked; private ArraySet<Uri> mGrantableUris; + // Storage for phone numbers that were found to be associated with + // contacts in this notification. + private ArraySet<String> mPhoneNumbers; + // Whether this notification record should have an update logged the next time notifications // are sorted. private boolean mPendingLogUpdate = false; @@ -1547,6 +1551,26 @@ public final class NotificationRecord { return mPendingLogUpdate; } + /** + * Merge the given set of phone numbers into the list of phone numbers that + * are cached on this notification record. + */ + public void mergePhoneNumbers(ArraySet<String> phoneNumbers) { + // if the given phone numbers are null or empty then don't do anything + if (phoneNumbers == null || phoneNumbers.size() == 0) { + return; + } + // initialize if not already + if (mPhoneNumbers == null) { + mPhoneNumbers = new ArraySet<>(); + } + mPhoneNumbers.addAll(phoneNumbers); + } + + public ArraySet<String> getPhoneNumbers() { + return mPhoneNumbers; + } + @VisibleForTesting static final class Light { public final int color; diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index d7bc3bb8af28..dc4d04feab72 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -68,7 +68,10 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { private static final boolean ENABLE_PEOPLE_VALIDATOR = true; private static final String SETTING_ENABLE_PEOPLE_VALIDATOR = "validate_notification_people_enabled"; - private static final String[] LOOKUP_PROJECTION = { Contacts._ID, Contacts.STARRED }; + private static final String[] LOOKUP_PROJECTION = { Contacts._ID, Contacts.LOOKUP_KEY, + Contacts.STARRED, Contacts.HAS_PHONE_NUMBER }; + private static final String[] PHONE_LOOKUP_PROJECTION = + { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }; private static final int MAX_PEOPLE = 10; private static final int PEOPLE_CACHE_SIZE = 200; @@ -409,6 +412,35 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return lookupResult; } + @VisibleForTesting + // Performs a contacts search using searchContacts, and then follows up by looking up + // any phone numbers associated with the resulting contact information and merge those + // into the lookup result as well. Will have no additional effect if the contact does + // not have any phone numbers. + LookupResult searchContactsAndLookupNumbers(Context context, Uri lookupUri) { + LookupResult lookupResult = searchContacts(context, lookupUri); + String phoneLookupKey = lookupResult.getPhoneLookupKey(); + if (phoneLookupKey != null) { + String selection = Contacts.LOOKUP_KEY + " = ?"; + String[] selectionArgs = new String[] { phoneLookupKey }; + try (Cursor cursor = context.getContentResolver().query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PHONE_LOOKUP_PROJECTION, + selection, selectionArgs, /* sortOrder= */ null)) { + if (cursor == null) { + Slog.w(TAG, "Cursor is null when querying contact phone number."); + return lookupResult; + } + + while (cursor.moveToNext()) { + lookupResult.mergePhoneNumber(cursor); + } + } catch (Throwable t) { + Slog.w(TAG, "Problem getting content resolver or querying phone numbers.", t); + } + } + return lookupResult; + } + private void addWorkContacts(LookupResult lookupResult, Context context, Uri corpLookupUri) { final int workUserId = findWorkUserId(context); if (workUserId == -1) { @@ -454,6 +486,9 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { private final long mExpireMillis; private float mAffinity = NONE; + private boolean mHasPhone = false; + private String mPhoneLookupKey = null; + private ArraySet<String> mPhoneNumbers = new ArraySet<>(); public LookupResult() { mExpireMillis = System.currentTimeMillis() + CONTACT_REFRESH_MILLIS; @@ -473,6 +508,15 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { Slog.i(TAG, "invalid cursor: no _ID"); } + // Lookup key for potentially looking up contact phone number later + final int lookupKeyIdx = cursor.getColumnIndex(Contacts.LOOKUP_KEY); + if (lookupKeyIdx >= 0) { + mPhoneLookupKey = cursor.getString(lookupKeyIdx); + if (DEBUG) Slog.d(TAG, "contact LOOKUP_KEY is: " + mPhoneLookupKey); + } else { + if (DEBUG) Slog.d(TAG, "invalid cursor: no LOOKUP_KEY"); + } + // Starred final int starIdx = cursor.getColumnIndex(Contacts.STARRED); if (starIdx >= 0) { @@ -484,6 +528,39 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } else { if (DEBUG) Slog.d(TAG, "invalid cursor: no STARRED"); } + + // whether a phone number is present + final int hasPhoneIdx = cursor.getColumnIndex(Contacts.HAS_PHONE_NUMBER); + if (hasPhoneIdx >= 0) { + mHasPhone = cursor.getInt(hasPhoneIdx) != 0; + if (DEBUG) Slog.d(TAG, "contact HAS_PHONE_NUMBER is: " + mHasPhone); + } else { + if (DEBUG) Slog.d(TAG, "invalid cursor: no HAS_PHONE_NUMBER"); + } + } + + // Returns the phone lookup key that is cached in this result, or null + // if the contact has no known phone info. + public String getPhoneLookupKey() { + if (!mHasPhone) { + return null; + } + return mPhoneLookupKey; + } + + // Merge phone number found in this lookup and store it in mPhoneNumbers. + public void mergePhoneNumber(Cursor cursor) { + final int phoneNumIdx = cursor.getColumnIndex( + ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER); + if (phoneNumIdx >= 0) { + mPhoneNumbers.add(cursor.getString(phoneNumIdx)); + } else { + if (DEBUG) Slog.d(TAG, "invalid cursor: no NORMALIZED_NUMBER"); + } + } + + public ArraySet<String> getPhoneNumbers() { + return mPhoneNumbers; } private boolean isExpired() { @@ -509,6 +586,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { // Amount of time to wait for a result from the contacts db before rechecking affinity. private static final long LOOKUP_TIME = 1000; private float mContactAffinity = NONE; + private ArraySet<String> mPhoneNumbers = null; private NotificationRecord mRecord; private PeopleRankingReconsideration(Context context, String key, @@ -543,7 +621,9 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart()); } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) { if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle); - lookupResult = searchContacts(mContext, uri); + // only look up phone number if this is a contact lookup uri and thus isn't + // already directly a phone number. + lookupResult = searchContactsAndLookupNumbers(mContext, uri); } else { lookupResult = new LookupResult(); // invalid person for the cache if (!"name".equals(uri.getScheme())) { @@ -561,6 +641,13 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity()); } mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity()); + // merge any phone numbers found in this lookup result + if (lookupResult.getPhoneNumbers() != null) { + if (mPhoneNumbers == null) { + mPhoneNumbers = new ArraySet<>(); + } + mPhoneNumbers.addAll(lookupResult.getPhoneNumbers()); + } } else { if (DEBUG) Slog.d(TAG, "lookupResult is null"); } @@ -581,6 +668,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { float affinityBound = operand.getContactAffinity(); operand.setContactAffinity(Math.max(mContactAffinity, affinityBound)); if (VERBOSE) Slog.i(TAG, "final affinity: " + operand.getContactAffinity()); + operand.mergePhoneNumbers(mPhoneNumbers); } public float getContactAffinity() { diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java index 29aad63a1f4b..d04b3315fcec 100644 --- a/services/core/java/com/android/server/notification/ZenModeFiltering.java +++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java @@ -33,6 +33,7 @@ import android.telecom.TelecomManager; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import com.android.internal.messages.nano.SystemMessageProto; @@ -140,7 +141,7 @@ public class ZenModeFiltering { } protected void recordCall(NotificationRecord record) { - REPEAT_CALLERS.recordCall(mContext, extras(record)); + REPEAT_CALLERS.recordCall(mContext, extras(record), record.getPhoneNumbers()); } /** @@ -351,7 +352,8 @@ public class ZenModeFiltering { private final ArrayMap<String, Long> mOtherCalls = new ArrayMap<>(); private int mThresholdMinutes; - private synchronized void recordCall(Context context, Bundle extras) { + private synchronized void recordCall(Context context, Bundle extras, + ArraySet<String> phoneNumbers) { setThresholdMinutes(context); if (mThresholdMinutes <= 0 || extras == null) return; final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras); @@ -359,7 +361,7 @@ public class ZenModeFiltering { final long now = System.currentTimeMillis(); cleanUp(mTelCalls, now); cleanUp(mOtherCalls, now); - recordCallers(extraPeople, now); + recordCallers(extraPeople, phoneNumbers, now); } private synchronized boolean isRepeat(Context context, Bundle extras) { @@ -407,7 +409,8 @@ public class ZenModeFiltering { } } - private synchronized void recordCallers(String[] people, long now) { + private synchronized void recordCallers(String[] people, ArraySet<String> phoneNumbers, + long now) { for (int i = 0; i < people.length; i++) { String person = people[i]; if (person == null) continue; @@ -428,6 +431,14 @@ public class ZenModeFiltering { mOtherCalls.put(person, now); } } + + // record any additional numbers from the notification record if + // provided; these are in the format of just a phone number string + if (phoneNumbers != null) { + for (String num : phoneNumbers) { + mTelCalls.put(num, now); + } + } } private synchronized boolean checkCallers(Context context, String[] people) { diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index ba89916e6dfa..53eb9cf7d9fe 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -293,7 +293,8 @@ final class DexOptHelper { public ArraySet<String> getOptimizablePackages() { ArraySet<String> pkgs = new ArraySet<>(); mPm.forEachPackageState(packageState -> { - if (mPm.mPackageDexOptimizer.canOptimizePackage(packageState.getPkg())) { + final AndroidPackage pkg = packageState.getPkg(); + if (pkg != null && mPm.mPackageDexOptimizer.canOptimizePackage(pkg)) { pkgs.add(packageState.getPackageName()); } }); diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index a5b42f03b6df..69d498794e64 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -182,7 +182,7 @@ public class PackageDexOptimizer { mInjector = injector; } - boolean canOptimizePackage(AndroidPackage pkg) { + boolean canOptimizePackage(@NonNull AndroidPackage pkg) { // We do not dexopt a package with no code. // Note that the system package is marked as having no code, however we can // still optimize it via dexoptSystemServerPath. 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 79c5ea2efefe..edc0e3d64c42 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -51,6 +51,7 @@ import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.permission.SplitPermissionInfoParcelable; import android.os.Binder; +import android.os.Build; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; @@ -613,11 +614,12 @@ public class PermissionManagerService extends IPermissionManager.Stub { int granted = PermissionManagerService.this.checkUidPermission(uid, POST_NOTIFICATIONS); AndroidPackage pkg = mPackageManagerInt.getPackage(uid); - if (granted != PermissionManager.PERMISSION_GRANTED) { + if (granted != PackageManager.PERMISSION_GRANTED + && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) { int flags = PermissionManagerService.this.getPermissionFlags(pkg.getPackageName(), POST_NOTIFICATIONS, UserHandle.getUserId(uid)); if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { - return PermissionManager.PERMISSION_GRANTED; + return PackageManager.PERMISSION_GRANTED; } } return granted; diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java index f519ceda1e50..243efb5e58ce 100644 --- a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java +++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java @@ -16,6 +16,8 @@ package com.android.server.security; +import static android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED; +import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE; import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN; import android.content.Context; @@ -76,10 +78,24 @@ public class AttestationVerificationManagerService extends SystemService { private void verifyAttestationForAllVerifiers( AttestationProfile profile, int localBindingType, Bundle requirements, byte[] attestation, AndroidFuture<IVerificationResult> resultCallback) { - // TODO(b/201696614): Implement IVerificationResult result = new IVerificationResult(); - result.resultCode = RESULT_UNKNOWN; + // TODO(b/201696614): Implement result.token = null; + switch (profile.getAttestationProfileId()) { + case PROFILE_SELF_TRUSTED: + Slog.d(TAG, "Verifying Self trusted profile."); + try { + result.resultCode = + AttestationVerificationSelfTrustedVerifierForTesting.getInstance() + .verifyAttestation(localBindingType, requirements, attestation); + } catch (Throwable t) { + result.resultCode = RESULT_FAILURE; + } + break; + default: + Slog.d(TAG, "No profile found, defaulting."); + result.resultCode = RESULT_UNKNOWN; + } resultCallback.complete(result); } diff --git a/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java b/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java new file mode 100644 index 000000000000..58df2bd982dc --- /dev/null +++ b/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.security; + +import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE; +import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE; +import static android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS; +import static android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE; + +import android.annotation.NonNull; +import android.os.Build; +import android.os.Bundle; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.org.bouncycastle.asn1.ASN1InputStream; +import com.android.internal.org.bouncycastle.asn1.ASN1ObjectIdentifier; +import com.android.internal.org.bouncycastle.asn1.ASN1OctetString; +import com.android.internal.org.bouncycastle.asn1.ASN1Sequence; +import com.android.internal.org.bouncycastle.asn1.x509.Certificate; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.CertPath; +import java.security.cert.CertPathValidator; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.PKIXParameters; +import java.security.cert.TrustAnchor; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Verifies {@code PROFILE_SELF_TRUSTED} attestations. + * + * Verifies that the attesting environment can create an attestation with the same root certificate + * as the verifying device with a matching attestation challenge. Skips CRL revocations checking + * so this verifier can work in a hermetic test environment. + * + * This verifier profile is intended to be used only for testing. + */ +class AttestationVerificationSelfTrustedVerifierForTesting { + private static final String TAG = "AVF"; + private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE); + + // The OID for the extension Android Keymint puts into device-generated certificates. + private static final String ANDROID_KEYMINT_KEY_DESCRIPTION_EXTENSION_OID = + "1.3.6.1.4.1.11129.2.1.17"; + + // ASN.1 sequence index values for the Android Keymint extension. + private static final int ATTESTATION_CHALLENGE_INDEX = 4; + + private static final String ANDROID_KEYSTORE = "AndroidKeyStore"; + private static final String GOLDEN_ALIAS = + AttestationVerificationSelfTrustedVerifierForTesting.class.getCanonicalName() + + ".Golden"; + + private static volatile AttestationVerificationSelfTrustedVerifierForTesting + sAttestationVerificationSelfTrustedVerifier = null; + + private final CertificateFactory mCertificateFactory; + private final CertPathValidator mCertPathValidator; + private final KeyStore mAndroidKeyStore; + private X509Certificate mGoldenRootCert; + + static AttestationVerificationSelfTrustedVerifierForTesting getInstance() + throws Exception { + if (sAttestationVerificationSelfTrustedVerifier == null) { + synchronized (AttestationVerificationSelfTrustedVerifierForTesting.class) { + if (sAttestationVerificationSelfTrustedVerifier == null) { + sAttestationVerificationSelfTrustedVerifier = + new AttestationVerificationSelfTrustedVerifierForTesting(); + } + } + } + return sAttestationVerificationSelfTrustedVerifier; + } + + private static void debugVerboseLog(String str, Throwable t) { + if (DEBUG) { + Slog.v(TAG, str, t); + } + } + + private static void debugVerboseLog(String str) { + if (DEBUG) { + Slog.v(TAG, str); + } + } + + private AttestationVerificationSelfTrustedVerifierForTesting() throws Exception { + mCertificateFactory = CertificateFactory.getInstance("X.509"); + mCertPathValidator = CertPathValidator.getInstance("PKIX"); + mAndroidKeyStore = KeyStore.getInstance(ANDROID_KEYSTORE); + mAndroidKeyStore.load(null); + if (!mAndroidKeyStore.containsAlias(GOLDEN_ALIAS)) { + KeyPairGenerator kpg = + KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, ANDROID_KEYSTORE); + KeyGenParameterSpec parameterSpec = new KeyGenParameterSpec.Builder( + GOLDEN_ALIAS, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) + .setAttestationChallenge(GOLDEN_ALIAS.getBytes()) + .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512).build(); + kpg.initialize(parameterSpec); + kpg.generateKeyPair(); + } + + X509Certificate[] goldenCerts = (X509Certificate[]) + ((KeyStore.PrivateKeyEntry) mAndroidKeyStore.getEntry(GOLDEN_ALIAS, null)) + .getCertificateChain(); + mGoldenRootCert = goldenCerts[goldenCerts.length - 1]; + } + + int verifyAttestation( + int localBindingType, @NonNull Bundle requirements, @NonNull byte[] attestation) { + List<X509Certificate> certificates = new ArrayList<>(); + ByteArrayInputStream bis = new ByteArrayInputStream(attestation); + try { + while (bis.available() > 0) { + certificates.add((X509Certificate) mCertificateFactory.generateCertificate(bis)); + } + } catch (CertificateException e) { + debugVerboseLog("Unable to parse certificates from attestation", e); + return RESULT_FAILURE; + } + + if (localBindingType == TYPE_CHALLENGE + && validateRequirements(requirements) + && checkLeafChallenge(requirements, certificates) + && verifyCertificateChain(certificates)) { + return RESULT_SUCCESS; + } + + return RESULT_FAILURE; + } + + private boolean verifyCertificateChain(List<X509Certificate> certificates) { + if (certificates.size() < 2) { + debugVerboseLog("Certificate chain less than 2 in size."); + return false; + } + + try { + CertPath certificatePath = mCertificateFactory.generateCertPath(certificates); + PKIXParameters validationParams = new PKIXParameters(getTrustAnchors()); + // Skipping revocation checking because we want this to work in a hermetic test + // environment. + validationParams.setRevocationEnabled(false); + mCertPathValidator.validate(certificatePath, validationParams); + } catch (Throwable t) { + debugVerboseLog("Invalid certificate chain", t); + return false; + } + + return true; + } + + private Set<TrustAnchor> getTrustAnchors() { + return Collections.singleton(new TrustAnchor(mGoldenRootCert, null)); + } + + private boolean validateRequirements(Bundle requirements) { + if (requirements.size() != 1) { + debugVerboseLog("Requirements does not contain exactly 1 key."); + return false; + } + if (!requirements.containsKey(PARAM_CHALLENGE)) { + debugVerboseLog("Requirements does not contain key: " + PARAM_CHALLENGE); + return false; + } + return true; + } + + private boolean checkLeafChallenge(Bundle requirements, List<X509Certificate> certificates) { + // Verify challenge + byte[] challenge; + try { + challenge = getChallengeFromCert(certificates.get(0)); + } catch (Throwable t) { + debugVerboseLog("Unable to parse challenge from certificate.", t); + return false; + } + + if (Arrays.equals(requirements.getByteArray(PARAM_CHALLENGE), challenge)) { + return true; + } else { + debugVerboseLog("Self-Trusted validation failed; challenge mismatch."); + return false; + } + } + + private byte[] getChallengeFromCert(@NonNull X509Certificate x509Certificate) + throws CertificateEncodingException, IOException { + Certificate certificate = Certificate.getInstance( + new ASN1InputStream(x509Certificate.getEncoded()).readObject()); + ASN1Sequence keyAttributes = (ASN1Sequence) certificate.getTBSCertificate().getExtensions() + .getExtensionParsedValue( + new ASN1ObjectIdentifier(ANDROID_KEYMINT_KEY_DESCRIPTION_EXTENSION_OID)); + return ((ASN1OctetString) keyAttributes.getObjectAt(ATTESTATION_CHALLENGE_INDEX)) + .getOctets(); + } +} diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 8a87c96fcaaa..94f483c65eb5 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -53,6 +53,7 @@ import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.fingerprint.IUdfpsHbmListener; +import android.media.MediaRoute2Info; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -92,6 +93,7 @@ import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.ISessionListener; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.statusbar.IUndoMediaTransferCallback; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.statusbar.RegisterStatusBarResult; import com.android.internal.statusbar.StatusBarIcon; @@ -1265,6 +1267,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D "StatusBarManagerService"); } + private void enforceMediaContentControl() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MEDIA_CONTENT_CONTROL, + "StatusBarManagerService"); + } + /** * For targetSdk S+ we require STATUS_BAR. For targetSdk < S, we only require EXPAND_STATUS_BAR * but also require that it falls into one of the allowed use-cases to lock down abuse vector. @@ -1987,6 +1995,53 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D return false; } + /** + * Notifies the system of a new media tap-to-transfer state for the *sender* device. See + * {@link StatusBarManager.updateMediaTapToTransferSenderDisplay} for more information. + * + * @param undoCallback a callback that will be triggered if the user elects to undo a media + * transfer. + * + * Requires the caller to have the {@link android.Manifest.permission.MEDIA_CONTENT_CONTROL} + * permission. + */ + @Override + public void updateMediaTapToTransferSenderDisplay( + @StatusBarManager.MediaTransferSenderState int displayState, + @NonNull MediaRoute2Info routeInfo, + @Nullable IUndoMediaTransferCallback undoCallback + ) { + enforceMediaContentControl(); + if (mBar != null) { + try { + mBar.updateMediaTapToTransferSenderDisplay(displayState, routeInfo, undoCallback); + } catch (RemoteException e) { + Slog.e(TAG, "updateMediaTapToTransferSenderDisplay", e); + } + } + } + + /** + * Notifies the system of a new media tap-to-transfer state for the *receiver* device. See + * {@link StatusBarManager.updateMediaTapToTransferReceiverDisplay} for more information. + * + * Requires the caller to have the {@link android.Manifest.permission.MEDIA_CONTENT_CONTROL} + * permission. + */ + @Override + public void updateMediaTapToTransferReceiverDisplay( + @StatusBarManager.MediaTransferReceiverState int displayState, + MediaRoute2Info routeInfo) { + enforceMediaContentControl(); + if (mBar != null) { + try { + mBar.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo); + } catch (RemoteException e) { + Slog.e(TAG, "updateMediaTapToTransferReceiverDisplay", e); + } + } + } + /** @hide */ public void passThroughShellCommand(String[] args, FileDescriptor fd) { enforceStatusBarOrShell(); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java index 28c91aaefd8b..76709536b40b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java @@ -101,6 +101,7 @@ import android.os.SystemClock; import android.os.UidBatteryConsumer; import android.os.UserHandle; import android.provider.DeviceConfig; +import android.telephony.TelephonyManager; import android.util.Log; import android.util.Pair; @@ -211,6 +212,7 @@ public final class BackgroundRestrictionTest { @Mock private PermissionManagerServiceInternal mPermissionManagerServiceInternal; @Mock private MediaSessionManager mMediaSessionManager; @Mock private RoleManager mRoleManager; + @Mock private TelephonyManager mTelephonyManager; private long mCurrentTimeMillis; @@ -2309,6 +2311,11 @@ public final class BackgroundRestrictionTest { } @Override + TelephonyManager getTelephonyManager() { + return mTelephonyManager; + } + + @Override AppFGSTracker getAppFGSTracker() { return mAppFGSTracker; } 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 bdeb2b4fd839..f9bdad6c62ba 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -204,6 +204,49 @@ public class JobSchedulerServiceTest { jobInfoBuilder.build(), callingUid, "com.android.test", 0, testTag); } + @Test + public void testGetMinJobExecutionGuaranteeMs() { + JobStatus ejMax = createJobStatus("testGetMinJobExecutionGuaranteeMs", + createJobInfo(1).setExpedited(true)); + JobStatus ejHigh = createJobStatus("testGetMinJobExecutionGuaranteeMs", + createJobInfo(2).setExpedited(true).setPriority(JobInfo.PRIORITY_HIGH)); + JobStatus ejMaxDowngraded = createJobStatus("testGetMinJobExecutionGuaranteeMs", + createJobInfo(3).setExpedited(true)); + JobStatus ejHighDowngraded = createJobStatus("testGetMinJobExecutionGuaranteeMs", + createJobInfo(4).setExpedited(true).setPriority(JobInfo.PRIORITY_HIGH)); + JobStatus jobHigh = createJobStatus("testGetMinJobExecutionGuaranteeMs", + createJobInfo(5).setPriority(JobInfo.PRIORITY_HIGH)); + JobStatus jobDef = createJobStatus("testGetMinJobExecutionGuaranteeMs", + createJobInfo(6)); + + spyOn(ejMax); + spyOn(ejHigh); + spyOn(ejMaxDowngraded); + spyOn(ejHighDowngraded); + spyOn(jobHigh); + spyOn(jobDef); + + when(ejMax.shouldTreatAsExpeditedJob()).thenReturn(true); + when(ejHigh.shouldTreatAsExpeditedJob()).thenReturn(true); + when(ejMaxDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false); + when(ejHighDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false); + when(jobHigh.shouldTreatAsExpeditedJob()).thenReturn(false); + when(jobDef.shouldTreatAsExpeditedJob()).thenReturn(false); + + assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS, + mService.getMinJobExecutionGuaranteeMs(ejMax)); + assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS, + mService.getMinJobExecutionGuaranteeMs(ejHigh)); + assertEquals(mService.mConstants.RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS, + mService.getMinJobExecutionGuaranteeMs(ejMaxDowngraded)); + assertEquals(mService.mConstants.RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS, + mService.getMinJobExecutionGuaranteeMs(ejHighDowngraded)); + assertEquals(mService.mConstants.RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS, + mService.getMinJobExecutionGuaranteeMs(jobHigh)); + assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS, + mService.getMinJobExecutionGuaranteeMs(jobDef)); + } + /** * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job * with the correct delay and deadline constraints if the periodic job is scheduled with the diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 153ce17ec9dd..9d6793ca4b7b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -30,6 +30,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; +import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX; import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RARE_INDEX; @@ -536,6 +537,7 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; @@ -595,6 +597,7 @@ public class QuotaControllerTest { assertNotNull(mQuotaController.getEJTimingSessions(10, "com.android.test")); ExecutionStats expectedStats = new ExecutionStats(); + expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; @@ -638,11 +641,13 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); ExecutionStats inputStats = new ExecutionStats(); + inputStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS; inputStats.jobCountLimit = expectedStats.jobCountLimit = 100; inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100; // Invalid time is now +24 hours since there are no sessions at all for the app. expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; synchronized (mQuotaController.mLock) { mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats); } @@ -827,6 +832,8 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); ExecutionStats inputStats = new ExecutionStats(); + inputStats.allowedTimePerPeriodMs = expectedStats.allowedTimePerPeriodMs = + 10 * MINUTE_IN_MILLIS; inputStats.windowSizeMs = expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; inputStats.jobCountLimit = expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; inputStats.sessionCountLimit = expectedStats.sessionCountLimit = @@ -924,6 +931,7 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); // Active + expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; @@ -1006,6 +1014,7 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); // Active + expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; @@ -1242,6 +1251,7 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 6 * HOUR_IN_MILLIS); ExecutionStats expectedStats = new ExecutionStats(); + expectedStats.allowedTimePerPeriodMs = originalStatsActive.allowedTimePerPeriodMs; expectedStats.windowSizeMs = originalStatsActive.windowSizeMs; expectedStats.jobCountLimit = originalStatsActive.jobCountLimit; expectedStats.sessionCountLimit = originalStatsActive.sessionCountLimit; @@ -1261,6 +1271,7 @@ public class QuotaControllerTest { assertTrue(originalStatsActive == newStatsActive); assertEquals(expectedStats, newStatsActive); + expectedStats.allowedTimePerPeriodMs = originalStatsWorking.allowedTimePerPeriodMs; expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs; expectedStats.jobCountLimit = originalStatsWorking.jobCountLimit; expectedStats.sessionCountLimit = originalStatsWorking.sessionCountLimit; @@ -1277,6 +1288,7 @@ public class QuotaControllerTest { assertTrue(originalStatsWorking == newStatsWorking); assertNotEquals(expectedStats, newStatsWorking); + expectedStats.allowedTimePerPeriodMs = originalStatsFrequent.allowedTimePerPeriodMs; expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs; expectedStats.jobCountLimit = originalStatsFrequent.jobCountLimit; expectedStats.sessionCountLimit = originalStatsFrequent.sessionCountLimit; @@ -1293,6 +1305,7 @@ public class QuotaControllerTest { assertTrue(originalStatsFrequent == newStatsFrequent); assertNotEquals(expectedStats, newStatsFrequent); + expectedStats.allowedTimePerPeriodMs = originalStatsRare.allowedTimePerPeriodMs; expectedStats.windowSizeMs = originalStatsRare.windowSizeMs; expectedStats.jobCountLimit = originalStatsRare.jobCountLimit; expectedStats.sessionCountLimit = originalStatsRare.sessionCountLimit; @@ -1354,7 +1367,8 @@ public class QuotaControllerTest { @Test public void testGetMaxJobExecutionTimeLocked_Regular_Active() { JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked_Regular_Active", 0); - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 10 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + 10 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 10 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 2 * HOUR_IN_MILLIS); setDischarging(); @@ -2886,11 +2900,12 @@ public class QuotaControllerTest { public void testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow() { // Set rate limiting period different from allowed time to confirm code sets based on // the former. - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 10 * MINUTE_IN_MILLIS); + final int standbyBucket = WORKING_INDEX; + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + 10 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 5 * MINUTE_IN_MILLIS); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - final int standbyBucket = WORKING_INDEX; JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked", 1); setStandbyBucket(standbyBucket, jobStatus); @@ -2953,8 +2968,8 @@ public class QuotaControllerTest { @Test public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() { // Make sure any new value is used correctly. - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, - mQcConstants.ALLOWED_TIME_PER_PERIOD_MS / 2); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS / 2); runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); @@ -2977,8 +2992,8 @@ public class QuotaControllerTest { // Make sure any new value is used correctly. setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, mQcConstants.IN_QUOTA_BUFFER_MS * 2); - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, - mQcConstants.ALLOWED_TIME_PER_PERIOD_MS / 2); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS / 2); setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, mQcConstants.MAX_EXECUTION_TIME_MS / 2); @@ -3002,7 +3017,8 @@ public class QuotaControllerTest { // Working set window size is 2 hours. final int standbyBucket = WORKING_INDEX; final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2; - final long remainingTimeMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_MS - contributionMs; + final long remainingTimeMs = + mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS - contributionMs; // Session straddles edge of bucket window. Only the contribution should be counted towards // the quota. @@ -3062,16 +3078,28 @@ public class QuotaControllerTest { @Test public void testConstantsUpdating_ValidValues() { - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 5 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, + 8 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + 5 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + 7 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, + 2 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 4 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + 11 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 2 * MINUTE_IN_MILLIS); setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, .7f); setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .2f); + setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 99 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 15 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 30 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 45 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, 60 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, 120 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 3 * HOUR_IN_MILLIS); + setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_EXEMPTED, 6000); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, 5000); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 4000); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 3000); @@ -3079,6 +3107,7 @@ public class QuotaControllerTest { setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, 2000); setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * MINUTE_IN_MILLIS); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 500); + setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_EXEMPTED, 600); setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, 500); setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 400); setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, 300); @@ -3088,6 +3117,7 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 10 * SECOND_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 7 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, 3 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 2 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 90 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 1 * HOUR_IN_MILLIS); @@ -3104,10 +3134,23 @@ public class QuotaControllerTest { 84 * SECOND_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 83 * SECOND_IN_MILLIS); - assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()); + assertEquals(8 * MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); + assertEquals(5 * MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]); + assertEquals(7 * MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]); + assertEquals(2 * MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]); + assertEquals(4 * MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]); + assertEquals(11 * MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]); assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs()); assertEquals(.7f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6); assertEquals(.2f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6); + assertEquals(99 * MINUTE_IN_MILLIS, + mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]); assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); assertEquals(45 * MINUTE_IN_MILLIS, @@ -3118,12 +3161,14 @@ public class QuotaControllerTest { assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getRateLimitingWindowMs()); assertEquals(500, mQuotaController.getMaxJobCountPerRateLimitingWindow()); + assertEquals(6000, mQuotaController.getBucketMaxJobCounts()[EXEMPTED_INDEX]); assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]); assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]); assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]); assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]); assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RESTRICTED_INDEX]); assertEquals(50, mQuotaController.getMaxSessionCountPerRateLimitingWindow()); + assertEquals(600, mQuotaController.getBucketMaxSessionCounts()[EXEMPTED_INDEX]); assertEquals(500, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]); assertEquals(400, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]); assertEquals(300, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]); @@ -3132,6 +3177,7 @@ public class QuotaControllerTest { assertEquals(10 * SECOND_IN_MILLIS, mQuotaController.getTimingSessionCoalescingDurationMs()); assertEquals(7 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs()); + assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]); assertEquals(2 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]); assertEquals(90 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]); assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]); @@ -3151,16 +3197,24 @@ public class QuotaControllerTest { @Test public void testConstantsUpdating_InvalidValues() { // Test negatives/too low. - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, -MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, -MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, -MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, -MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, -MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, -MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, -MINUTE_IN_MILLIS); setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, -.1f); setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, -.01f); + setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, -MINUTE_IN_MILLIS); + setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_EXEMPTED, -1); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, -1); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 1); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 1); @@ -3168,6 +3222,7 @@ public class QuotaControllerTest { setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, -1); setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * SECOND_IN_MILLIS); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 0); + setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_EXEMPTED, -1); setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, -1); setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 0); setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, -3); @@ -3176,6 +3231,7 @@ public class QuotaControllerTest { setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 0); setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, -1); setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, -1); + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, -1); @@ -3191,10 +3247,19 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, -1); - assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()); + assertEquals(MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); + assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]); + assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]); + assertEquals(MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]); + assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]); + assertEquals(MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]); assertEquals(0, mQuotaController.getInQuotaBufferMs()); assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6); assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6); + assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); @@ -3203,12 +3268,14 @@ public class QuotaControllerTest { assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRateLimitingWindowMs()); assertEquals(10, mQuotaController.getMaxJobCountPerRateLimitingWindow()); + assertEquals(10, mQuotaController.getBucketMaxJobCounts()[EXEMPTED_INDEX]); assertEquals(10, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]); assertEquals(10, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]); assertEquals(10, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]); assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]); assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RESTRICTED_INDEX]); assertEquals(10, mQuotaController.getMaxSessionCountPerRateLimitingWindow()); + assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[EXEMPTED_INDEX]); assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]); assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]); assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]); @@ -3216,6 +3283,7 @@ public class QuotaControllerTest { assertEquals(0, mQuotaController.getBucketMaxSessionCounts()[RESTRICTED_INDEX]); assertEquals(0, mQuotaController.getTimingSessionCoalescingDurationMs()); assertEquals(0, mQuotaController.getMinQuotaCheckDelayMs()); + assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]); assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]); assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]); assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]); @@ -3233,17 +3301,37 @@ public class QuotaControllerTest { // Invalid configurations. // In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 2 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, + 10 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + 10 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + 10 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, + 2 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 10 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + 10 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 5 * MINUTE_IN_MILLIS); assertTrue(mQuotaController.getInQuotaBufferMs() - <= mQuotaController.getAllowedTimePerPeriodMs()); + <= mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]); // Test larger than a day. Controller should cap at one day. - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, + 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, + 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, 1f); setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .95f); + setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 25 * HOUR_IN_MILLIS); @@ -3254,6 +3342,7 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 25 * HOUR_IN_MILLIS); @@ -3269,10 +3358,21 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 25 * HOUR_IN_MILLIS); - assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()); + assertEquals(24 * HOUR_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); + assertEquals(24 * HOUR_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]); + assertEquals(24 * HOUR_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]); + assertEquals(24 * HOUR_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]); + assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]); + assertEquals(24 * HOUR_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]); assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs()); assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6); assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6); + assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); @@ -3284,6 +3384,7 @@ public class QuotaControllerTest { assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getTimingSessionCoalescingDurationMs()); assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs()); + assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]); diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java index 9e1445cf589d..bdea679a3311 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java @@ -30,7 +30,7 @@ import android.content.Context; import android.content.Intent; import android.media.AudioManager; import android.media.AudioSystem; -import android.media.BtProfileConnectionInfo; +import android.media.BluetoothProfileConnectionInfo; import android.util.Log; import androidx.test.InstrumentationRegistry; @@ -100,7 +100,7 @@ public class AudioDeviceBrokerTest { mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged( new AudioDeviceBroker.BtDeviceChangedData(mFakeBtDevice, null, - BtProfileConnectionInfo.a2dpInfo(true, 1), "testSource")); + BluetoothProfileConnectionInfo.createA2dpInfo(true, 1), "testSource")); Thread.sleep(2 * MAX_MESSAGE_HANDLING_DELAY_MS); verify(mSpyDevInventory, times(1)).setBluetoothActiveDevice( any(AudioDeviceBroker.BtDeviceInfo.class) @@ -208,13 +208,13 @@ public class AudioDeviceBrokerTest { // first connection: ensure the device is connected as a starting condition for the test mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged( new AudioDeviceBroker.BtDeviceChangedData(mFakeBtDevice, null, - BtProfileConnectionInfo.a2dpInfo(true, 1), "testSource")); + BluetoothProfileConnectionInfo.createA2dpInfo(true, 1), "testSource")); Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); // disconnection mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged( new AudioDeviceBroker.BtDeviceChangedData(null, mFakeBtDevice, - BtProfileConnectionInfo.a2dpInfo(false, -1), "testSource")); + BluetoothProfileConnectionInfo.createA2dpInfo(false, -1), "testSource")); if (delayAfterDisconnection > 0) { Thread.sleep(delayAfterDisconnection); } @@ -222,7 +222,7 @@ public class AudioDeviceBrokerTest { // reconnection mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged( new AudioDeviceBroker.BtDeviceChangedData(mFakeBtDevice, null, - BtProfileConnectionInfo.a2dpInfo(true, 2), "testSource")); + BluetoothProfileConnectionInfo.createA2dpInfo(true, 2), "testSource")); Thread.sleep(AudioService.BECOMING_NOISY_DELAY_MS + MAX_MESSAGE_HANDLING_DELAY_MS); // Verify disconnection has been cancelled and we're seeing two connections attempts, diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java index ae7b494fffbb..8e756aea27a9 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java @@ -320,8 +320,10 @@ public final class HdmiCecConfigTest { @Test public void getDefaultStringValue_MultipleDefaults() { setBooleanResource(R.bool.config_cecPowerControlModeBroadcast_default, true); - assertThrows(RuntimeException.class, - () -> new HdmiCecConfig(mContext, mStorageAdapter)); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); + assertThat(hdmiCecConfig.getDefaultStringValue( + HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)) + .isEqualTo(HdmiControlManager.POWER_CONTROL_MODE_BROADCAST); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index 75420337a63e..7e27e5438a0c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -66,6 +66,7 @@ import android.os.Vibrator; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.StatusBarNotification; +import android.util.ArraySet; import android.widget.RemoteViews; import androidx.test.filters.SmallTest; @@ -1304,4 +1305,45 @@ public class NotificationRecordTest extends UiServiceTestCase { assertFalse(record.isConversation()); } + + @Test + public void mergePhoneNumbers_nulls() { + // make sure nothing dies if we just don't have any phone numbers + StatusBarNotification sbn = getNotification(PKG_N_MR1, true /* noisy */, + true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, + false /* lights */, false /* defaultLights */, null /* group */); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); + + // by default, no phone numbers + assertNull(record.getPhoneNumbers()); + + // nothing happens if we attempt to merge phone numbers but there aren't any + record.mergePhoneNumbers(null); + assertNull(record.getPhoneNumbers()); + } + + @Test + public void mergePhoneNumbers_addNumbers() { + StatusBarNotification sbn = getNotification(PKG_N_MR1, true /* noisy */, + true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, + false /* lights */, false /* defaultLights */, null /* group */); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); + + // by default, no phone numbers + assertNull(record.getPhoneNumbers()); + + // make sure it behaves properly when we merge in some real content + record.mergePhoneNumbers(new ArraySet<>( + new String[]{"16175551212", "16175552121"})); + assertTrue(record.getPhoneNumbers().contains("16175551212")); + assertTrue(record.getPhoneNumbers().contains("16175552121")); + assertFalse(record.getPhoneNumbers().contains("16175553434")); + + // now merge in a new number, make sure old ones are still there and the new one + // is also there + record.mergePhoneNumbers(new ArraySet<>(new String[]{"16175553434"})); + assertTrue(record.getPhoneNumbers().contains("16175551212")); + assertTrue(record.getPhoneNumbers().contains("16175552121")); + assertTrue(record.getPhoneNumbers().contains("16175553434")); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java index 0bf105d62053..0552a8350329 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java @@ -19,8 +19,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -29,6 +34,7 @@ import android.app.Person; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; +import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.UserManager; @@ -43,6 +49,8 @@ import com.android.server.UiServiceTestCase; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Arrays; @@ -240,6 +248,118 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { assertFalse(ContentProvider.uriHasUserId(queryUri.getValue())); } + @Test + public void testMergePhoneNumbers_noPhoneNumber() { + // If merge phone number is called but the contacts lookup turned up no available + // phone number (HAS_PHONE_NUMBER is false), then no query should happen. + + // setup of various bits required for querying + final Context mockContext = mock(Context.class); + final ContentResolver mockContentResolver = mock(ContentResolver.class); + when(mockContext.getContentResolver()).thenReturn(mockContentResolver); + final int contactId = 12345; + final Uri lookupUri = Uri.withAppendedPath( + ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId)); + + // when the contact is looked up, we return a cursor that has one entry whose info is: + // _ID: 1 + // LOOKUP_KEY: "testlookupkey" + // STARRED: 0 + // HAS_PHONE_NUMBER: 0 + Cursor cursor = makeMockCursor(1, "testlookupkey", 0, 0); + when(mockContentResolver.query(any(), any(), any(), any(), any())).thenReturn(cursor); + + // call searchContacts and then mergePhoneNumbers, make sure we never actually + // query the content resolver for a phone number + new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri); + verify(mockContentResolver, never()).query( + eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI), + eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }), + contains(ContactsContract.Contacts.LOOKUP_KEY), + any(), // selection args + isNull()); // sort order + } + + @Test + public void testMergePhoneNumbers_hasNumber() { + // If merge phone number is called and the contact lookup has a phone number, + // make sure there's then a subsequent query for the phone number. + + // setup of various bits required for querying + final Context mockContext = mock(Context.class); + final ContentResolver mockContentResolver = mock(ContentResolver.class); + when(mockContext.getContentResolver()).thenReturn(mockContentResolver); + final int contactId = 12345; + final Uri lookupUri = Uri.withAppendedPath( + ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId)); + + // when the contact is looked up, we return a cursor that has one entry whose info is: + // _ID: 1 + // LOOKUP_KEY: "testlookupkey" + // STARRED: 0 + // HAS_PHONE_NUMBER: 1 + Cursor cursor = makeMockCursor(1, "testlookupkey", 0, 1); + + // make sure to add some specifics so this cursor is only returned for the + // contacts database lookup. + when(mockContentResolver.query(eq(lookupUri), any(), + isNull(), isNull(), isNull())).thenReturn(cursor); + + // in the case of a phone lookup, return null cursor; that's not an error case + // and we're not checking the actual storing of the phone data here. + when(mockContentResolver.query(eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI), + eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }), + contains(ContactsContract.Contacts.LOOKUP_KEY), + any(), isNull())).thenReturn(null); + + // call searchContacts and then mergePhoneNumbers, and check that we query + // once for the + new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri); + verify(mockContentResolver, times(1)).query( + eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI), + eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }), + contains(ContactsContract.Contacts.LOOKUP_KEY), + eq(new String[] { "testlookupkey" }), // selection args + isNull()); // sort order + } + + // Creates a cursor that points to one item of Contacts data with the specified + // columns. + private Cursor makeMockCursor(int id, String lookupKey, int starred, int hasPhone) { + Cursor mockCursor = mock(Cursor.class); + when(mockCursor.moveToFirst()).thenReturn(true); + doAnswer(new Answer<Boolean>() { + boolean mAccessed = false; + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + if (!mAccessed) { + mAccessed = true; + return true; + } + return false; + } + + }).when(mockCursor).moveToNext(); + + // id + when(mockCursor.getColumnIndex(ContactsContract.Contacts._ID)).thenReturn(0); + when(mockCursor.getInt(0)).thenReturn(id); + + // lookup key + when(mockCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)).thenReturn(1); + when(mockCursor.getString(1)).thenReturn(lookupKey); + + // starred + when(mockCursor.getColumnIndex(ContactsContract.Contacts.STARRED)).thenReturn(2); + when(mockCursor.getInt(2)).thenReturn(starred); + + // has phone number + when(mockCursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)).thenReturn(3); + when(mockCursor.getInt(3)).thenReturn(hasPhone); + + return mockCursor; + } + private void assertStringArrayEquals(String message, String[] expected, String[] result) { String expectedString = Arrays.toString(expected); String resultString = Arrays.toString(result); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java index 0f18cc61a95a..abcc8c1e99cb 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java @@ -50,11 +50,13 @@ import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.ArraySet; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.util.NotificationMessagingUtil; import com.android.server.UiServiceTestCase; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -72,6 +74,8 @@ public class ZenModeFilteringTest extends UiServiceTestCase { @Mock private TelephonyManager mTelephonyManager; + private long mTestStartTime; + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -79,6 +83,13 @@ public class ZenModeFilteringTest extends UiServiceTestCase { // for repeat callers / matchesCallFilter mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager); + mTestStartTime = System.currentTimeMillis(); + } + + @After + public void tearDown() { + // make sure to get rid of any data stored in repeat callers + mZenModeFiltering.cleanUpCallersAfter(mTestStartTime); } private NotificationRecord getNotificationRecord() { @@ -108,7 +119,10 @@ public class ZenModeFilteringTest extends UiServiceTestCase { return extras; } - private NotificationRecord getNotificationRecordWithPeople(String[] people) { + // Create a notification record with the people String array as the + // bundled extras, and the numbers ArraySet as additional phone numbers. + private NotificationRecord getRecordWithPeopleInfo(String[] people, + ArraySet<String> numbers) { // set up notification record NotificationRecord r = mock(NotificationRecord.class); StatusBarNotification sbn = mock(StatusBarNotification.class); @@ -116,6 +130,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase { notification.extras = makeExtrasBundleWithPeople(people); when(sbn.getNotification()).thenReturn(notification); when(r.getSbn()).thenReturn(sbn); + when(r.getPhoneNumbers()).thenReturn(numbers); return r; } @@ -339,7 +354,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase { // after calls given an email with an exact string match, make sure that // matchesCallFilter returns the right thing String[] mailSource = new String[]{"mailto:hello.world"}; - mZenModeFiltering.recordCall(getNotificationRecordWithPeople(mailSource)); + mZenModeFiltering.recordCall(getRecordWithPeopleInfo(mailSource, null)); // set up policy to only allow repeat callers Policy policy = new Policy( @@ -362,7 +377,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase { when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us"); String[] telSource = new String[]{"tel:+1-617-555-1212"}; - mZenModeFiltering.recordCall(getNotificationRecordWithPeople(telSource)); + mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null)); // set up policy to only allow repeat callers Policy policy = new Policy( @@ -406,7 +421,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase { when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us"); String[] telSource = new String[]{"tel:%2B16175551212"}; - mZenModeFiltering.recordCall(getNotificationRecordWithPeople(telSource)); + mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null)); // set up policy to only allow repeat callers Policy policy = new Policy( @@ -419,25 +434,64 @@ public class ZenModeFilteringTest extends UiServiceTestCase { Bundle different1 = makeExtrasBundleWithPeople(new String[]{"tel:%2B16175553434"}); Bundle different2 = makeExtrasBundleWithPeople(new String[]{"tel:+16175553434"}); - assertTrue("same number should match", + assertTrue("same number 1 should match", ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, UserHandle.SYSTEM, same1, null, 0, 0)); - assertTrue("same number should match", + assertTrue("same number 2 should match", ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, UserHandle.SYSTEM, same2, null, 0, 0)); - assertTrue("same number should match", + assertTrue("same number 3 should match", ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, UserHandle.SYSTEM, same3, null, 0, 0)); - assertFalse("different number should not match", + assertFalse("different number 1 should not match", ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, UserHandle.SYSTEM, different1, null, 0, 0)); - assertFalse("different number should not match", + assertFalse("different number 2 should not match", ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, UserHandle.SYSTEM, different2, null, 0, 0)); } + + @Test + public void testMatchesCallFilter_repeatCallers_viaRecordPhoneNumbers() { + // make sure that phone numbers that are passed in via the NotificationRecord's + // cached phone numbers field (from a contact lookup if the record is provided a contact + // uri) also get recorded in the repeat callers list. + + // set up telephony manager behavior + when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us"); + + String[] contactSource = new String[]{"content://contacts/lookup/uri-here"}; + ArraySet<String> contactNumbers = new ArraySet<>( + new String[]{"1-617-555-1212", "1-617-555-3434"}); + NotificationRecord record = getRecordWithPeopleInfo(contactSource, contactNumbers); + record.mergePhoneNumbers(contactNumbers); + mZenModeFiltering.recordCall(record); + + // set up policy to only allow repeat callers + Policy policy = new Policy( + PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE); + + // both phone numbers should register here + Bundle tel1 = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"}); + Bundle tel2 = makeExtrasBundleWithPeople(new String[]{"tel:16175553434"}); + Bundle different = makeExtrasBundleWithPeople(new String[]{"tel:16175555656"}); + + assertTrue("contact number 1 should match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + tel1, null, 0, 0)); + assertTrue("contact number 2 should match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + tel2, null, 0, 0)); + assertFalse("different number should not match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + different, null, 0, 0)); + } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 98a41bcf5adf..4a761a7a47be 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -1814,6 +1814,7 @@ public class UsageStatsService extends SystemService implements synchronized (mLock) { mResponseStatsTracker.dump(idpw); } + return; } else if (arg != null && !arg.startsWith("-")) { // Anything else that doesn't start with '-' is a pkg to filter pkgs.add(arg); diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt index 48bfd6f5d33c..62902929fcd5 100644 --- a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt +++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt @@ -11,10 +11,20 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import com.google.common.truth.Truth.assertThat +import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE +import android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE import android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED -import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY +import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE +import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN +import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY +import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties import java.lang.IllegalArgumentException +import java.io.ByteArrayOutputStream +import java.security.KeyPairGenerator +import java.security.KeyStore import java.time.Duration import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit @@ -23,25 +33,26 @@ import java.util.concurrent.TimeUnit @SmallTest @RunWith(AndroidJUnit4::class) class SystemAttestationVerificationTest { - @get:Rule val rule = ActivityScenarioRule(TestActivity::class.java) private lateinit var activity: Activity private lateinit var avm: AttestationVerificationManager + private lateinit var androidKeystore: KeyStore @Before fun setup() { rule.getScenario().onActivity { avm = it.getSystemService(AttestationVerificationManager::class.java) activity = it + androidKeystore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } } } @Test fun verifyAttestation_returnsUnknown() { val future = CompletableFuture<Int>() - val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + val profile = AttestationProfile(PROFILE_PEER_DEVICE) avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), activity.mainExecutor) { result, _ -> future.complete(result) @@ -51,9 +62,82 @@ class SystemAttestationVerificationTest { } @Test - fun verifyToken_returnsUnknown() { + fun verifyAttestation_returnsFailureWithEmptyAttestation() { val future = CompletableFuture<Int>() val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + avm.verifyAttestation(profile, TYPE_CHALLENGE, Bundle(), ByteArray(0), + activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureWithEmptyRequirements() { + val future = CompletableFuture<Int>() + val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") + avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, + Bundle(), selfTrusted.attestation, activity.mainExecutor) { result, _ -> + future.complete(result) + } + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureWithWrongBindingType() { + val future = CompletableFuture<Int>() + val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") + avm.verifyAttestation(selfTrusted.profile, TYPE_PUBLIC_KEY, + selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> + future.complete(result) + } + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureWithWrongRequirements() { + val future = CompletableFuture<Int>() + val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") + val wrongKeyRequirements = Bundle() + wrongKeyRequirements.putByteArray( + "wrongBindingKey", "challengeStr".encodeToByteArray()) + avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, + wrongKeyRequirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> + future.complete(result) + } + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + @Test + fun verifyAttestation_returnsFailureWithWrongChallenge() { + val future = CompletableFuture<Int>() + val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") + val wrongChallengeRequirements = Bundle() + wrongChallengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray()) + avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, + wrongChallengeRequirements, selfTrusted.attestation, activity.mainExecutor) { + result, _ -> future.complete(result) + } + assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + } + + // TODO(b/216144791): Add more failure tests for PROFILE_SELF_TRUSTED. + @Test + fun verifyAttestation_returnsSuccess() { + val future = CompletableFuture<Int>() + val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") + avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, + selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> + future.complete(result) + } + assertThat(future.getSoon()).isEqualTo(RESULT_SUCCESS) + } + + @Test + fun verifyToken_returnsUnknown() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_PEER_DEVICE) avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), activity.mainExecutor) { _, token -> val result = avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), token, null) @@ -66,7 +150,7 @@ class SystemAttestationVerificationTest { @Test fun verifyToken_tooBigMaxAgeThrows() { val future = CompletableFuture<VerificationToken>() - val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + val profile = AttestationProfile(PROFILE_PEER_DEVICE) avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), activity.mainExecutor) { _, token -> future.complete(token) @@ -87,4 +171,52 @@ class SystemAttestationVerificationTest { super.onCreate(savedInstanceState) } } + + inner class TestSelfTrustedAttestation(val alias: String, val challenge: String) { + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + val localBindingType = TYPE_CHALLENGE + val requirements: Bundle + val attestation: ByteArray + + init { + val challengeByteArray = challenge.encodeToByteArray() + generateAndStoreKey(alias, challengeByteArray) + attestation = generateCertificatesByteArray(alias) + requirements = Bundle() + requirements.putByteArray(PARAM_CHALLENGE, challengeByteArray) + } + + private fun generateAndStoreKey(alias: String, challenge: ByteArray) { + val kpg: KeyPairGenerator = KeyPairGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_EC, + ANDROID_KEYSTORE + ) + val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder( + alias, + KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY + ).run { + // a challenge results in a generated attestation + setAttestationChallenge(challenge) + setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) + build() + } + kpg.initialize(parameterSpec) + kpg.generateKeyPair() + } + + private fun generateCertificatesByteArray(alias: String): ByteArray { + val pkEntry = androidKeystore.getEntry(alias, null) as KeyStore.PrivateKeyEntry + val certs = pkEntry.certificateChain + val bos = ByteArrayOutputStream() + certs.forEach { + bos.write(it.encoded) + } + return bos.toByteArray() + } + } + + companion object { + private const val TAG = "AVFTEST" + private const val ANDROID_KEYSTORE = "AndroidKeyStore" + } } diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp index 41f73cd9c706..228520e8545b 100644 --- a/tests/vcn/Android.bp +++ b/tests/vcn/Android.bp @@ -18,6 +18,7 @@ android_test { "java/**/*.kt", ], platform_apis: true, + defaults: ["framework-connectivity-test-defaults"], test_suites: ["device-tests"], certificate: "platform", static_libs: [ @@ -28,6 +29,7 @@ android_test { "net-tests-utils", "platform-test-annotations", "services.core", + "service-connectivity-tiramisu-pre-jarjar", ], libs: [ "android.test.runner", diff --git a/tests/vcn/AndroidManifest.xml b/tests/vcn/AndroidManifest.xml index 2ad9aac67029..a8f657c89f76 100644 --- a/tests/vcn/AndroidManifest.xml +++ b/tests/vcn/AndroidManifest.xml @@ -16,7 +16,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.frameworks.tests.vcn"> - + <uses-sdk android:minSdkVersion="33" + android:targetSdkVersion="33"/> <application> <uses-library android:name="android.test.runner" /> </application> |