Merge changes Ic8f22359,I3b94dee1
* changes:
Don't query settings for the notification history state every time we update the footer.
Add trace sections for comparison of the notification pipelines
diff --git a/Android.bp b/Android.bp
index 838f304..e03f844 100644
--- a/Android.bp
+++ b/Android.bp
@@ -169,7 +169,6 @@
"framework-statsd.impl",
"framework-supplementalprocess.impl",
"framework-tethering.impl",
- "framework-nearby.impl",
"framework-uwb.impl",
"framework-wifi.impl",
"updatable-media",
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index afad29c..c4795f5 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -364,6 +364,11 @@
* @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 @@
REASON_ROLE_DIALER,
REASON_ROLE_EMERGENCY,
REASON_SYSTEM_MODULE,
+ REASON_CARRIER_PRIVILEGED_APP,
REASON_OPT_OUT_REQUESTED,
})
@Retention(RetentionPolicy.SOURCE)
@@ -749,6 +755,8 @@
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/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 9b1f2d0..13ecd25 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -3,6 +3,7 @@
import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.ActivityManager.ProcessState;
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageStatsManager.ForcedReasons;
import android.app.usage.UsageStatsManager.StandbyBuckets;
@@ -216,4 +217,19 @@
void dumpState(String[] args, PrintWriter pw);
boolean isAppIdleEnabled();
+
+ /**
+ * Returns the duration (in millis) for the window where events occurring will be
+ * considered as broadcast response, starting from the point when an app receives
+ * a broadcast.
+ */
+ long getBroadcastResponseWindowDurationMs();
+
+ /**
+ * Returns the process state threshold that should be used for deciding whether or not an app
+ * is in the background in the context of recording broadcast response stats. Apps whose
+ * process state is higher than this threshold state should be considered to be in background.
+ */
+ @ProcessState
+ int getBroadcastResponseFgThresholdState();
}
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 b2ae8ee..cea1945 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 @@
// 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 @@
shouldForceBatchJob =
mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
&& job.getEffectiveStandbyBucket() != ACTIVE_INDEX
+ && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
&& !batchDelayExpired;
}
@@ -2764,7 +2768,7 @@
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 @@
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 0eea701..0456a9b 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 @@
* 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 dd5246a..c1728a3 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.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 long expirationTimeElapsed;
+ public long allowedTimePerPeriodMs;
public long windowSizeMs;
public int jobCountLimit;
public int sessionCountLimit;
@@ -213,6 +215,7 @@
@Override
public String toString() {
return "expirationTime=" + expirationTimeElapsed + ", "
+ + "allowedTimePerPeriodMs=" + allowedTimePerPeriodMs + ", "
+ "windowSizeMs=" + windowSizeMs + ", "
+ "jobCountLimit=" + jobCountLimit + ", "
+ "sessionCountLimit=" + sessionCountLimit + ", "
@@ -236,6 +239,7 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE,
0, // NEVER
QcConstants.DEFAULT_MAX_SESSION_COUNT_RESTRICTED,
+ QcConstants.DEFAULT_MAX_SESSION_COUNT_EXEMPTED,
};
/**
@@ -506,7 +516,8 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
// 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 @@
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 @@
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 @@
} 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 @@
// 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 @@
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 @@
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 @@
}
}
final boolean inRegularQuota =
- stats.executionTimeInWindowMs < getAllowedTimePerPeriodMs(minPriority)
+ stats.executionTimeInWindowMs
+ < getAllowedTimePerPeriodMs(standbyBucket, minPriority)
&& stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
&& isUnderJobCountQuota
&& isUnderTimingSessionCountQuota;
@@ -2921,9 +2951,29 @@
/** 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_MS =
- QC_CONSTANT_PREFIX + "allowed_time_per_period_ms";
+ 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_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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 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 @@
/**
* 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 @@
* 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 @@
* 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 @@
/**
* 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 @@
* 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 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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
//////////////////////// TESTING HELPERS /////////////////////////////
@VisibleForTesting
- long getAllowedTimePerPeriodMs() {
+ long[] getAllowedTimePerPeriodMs() {
return mAllowedTimePerPeriodMs;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 8b26397..b843dca 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -347,6 +347,22 @@
*/
boolean mLinkCrossProfileApps =
ConstantsObserver.DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS;
+
+ /**
+ * Duration (in millis) for the window where events occurring will be considered as
+ * broadcast response, starting from the point when an app receives a broadcast.
+ */
+ volatile long mBroadcastResponseWindowDurationMillis =
+ ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS;
+
+ /**
+ * Process state threshold that is used for deciding whether or not an app is in the background
+ * in the context of recording broadcast response stats. Apps whose process state is higher
+ * than this threshold state will be considered to be in background.
+ */
+ volatile int mBroadcastResponseFgThresholdState =
+ ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE;
+
/**
* Whether we should allow apps into the
* {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket or not.
@@ -1774,6 +1790,15 @@
}
}
+ @Override
+ public long getBroadcastResponseWindowDurationMs() {
+ return mBroadcastResponseWindowDurationMillis;
+ }
+
+ @Override
+ public int getBroadcastResponseFgThresholdState() {
+ return mBroadcastResponseFgThresholdState;
+ }
@Override
public void flushToDisk() {
@@ -2042,6 +2067,14 @@
TimeUtils.formatDuration(mSystemUpdateUsageTimeoutMillis, pw);
pw.println();
+ pw.print(" mBroadcastResponseWindowDurationMillis=");
+ TimeUtils.formatDuration(mBroadcastResponseWindowDurationMillis, pw);
+ pw.println();
+
+ pw.print(" mBroadcastResponseFgThresholdState=");
+ pw.print(ActivityManager.procStateToString(mBroadcastResponseFgThresholdState));
+ pw.println();
+
pw.println();
pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
pw.print(" mAllowRestrictedBucket=");
@@ -2473,6 +2506,10 @@
KEY_PREFIX_ELAPSED_TIME_THRESHOLD + "rare",
KEY_PREFIX_ELAPSED_TIME_THRESHOLD + "restricted"
};
+ private static final String KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS =
+ "broadcast_response_window_timeout_ms";
+ private static final String KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE =
+ "broadcast_response_fg_threshold_state";
public static final long DEFAULT_CHECK_IDLE_INTERVAL_MS =
COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR;
public static final long DEFAULT_STRONG_USAGE_TIMEOUT =
@@ -2502,6 +2539,10 @@
public static final long DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS =
COMPRESS_TIME ? ONE_MINUTE : ONE_DAY;
public static final boolean DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS = true;
+ public static final long DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS =
+ 2 * ONE_MINUTE;
+ public static final int DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE =
+ ActivityManager.PROCESS_STATE_TOP;
ConstantsObserver(Handler handler) {
super(handler);
@@ -2619,6 +2660,16 @@
KEY_UNEXEMPTED_SYNC_SCHEDULED_HOLD_DURATION,
DEFAULT_UNEXEMPTED_SYNC_SCHEDULED_TIMEOUT);
break;
+ case KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS:
+ mBroadcastResponseWindowDurationMillis = properties.getLong(
+ KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS,
+ DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS);
+ break;
+ case KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE:
+ mBroadcastResponseFgThresholdState = properties.getInt(
+ KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE,
+ DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE);
+ break;
default:
if (!timeThresholdsUpdated
&& (name.startsWith(KEY_PREFIX_SCREEN_TIME_THRESHOLD)
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
index c5dc51c..e407e31 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -5,7 +5,9 @@
"options": [
{"include-filter": "android.app.usage.cts.UsageStatsTest"},
{"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.MediumTest"},
+ {"exclude-annotation": "androidx.test.filters.LargeTest"}
]
},
{
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index d963e68..b4edd39 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -61,6 +61,9 @@
"test_com.android.media",
],
min_sdk_version: "29",
+ lint: {
+ strict_updatability_linting: true,
+ },
visibility: [
"//frameworks/av/apex:__subpackages__",
"//frameworks/base", // For framework-all
diff --git a/apex/media/framework/lint-baseline.xml b/apex/media/framework/lint-baseline.xml
index e1b1450..95eea45 100644
--- a/apex/media/framework/lint-baseline.xml
+++ b/apex/media/framework/lint-baseline.xml
@@ -1,312 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev">
<issue
- id="NewApi"
- message="Call requires API level 31 (current min is 29): `new android.media.ApplicationMediaCapabilities.Builder`"
- errorLine1=" new ApplicationMediaCapabilities.Builder();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ id="DefaultLocale"
+ message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+ errorLine1=" if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {"
+ errorLine2=" ~~~~~~~~~~~">
<location
file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java"
- line="208"
- column="29"/>
+ line="121"
+ column="57"/>
</issue>
<issue
- id="NewApi"
- message="Call requires API level 31 (current min is 29): `new android.media.ApplicationMediaCapabilities.Builder`"
- errorLine1=" ApplicationMediaCapabilities.Builder builder = new ApplicationMediaCapabilities.Builder();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ id="DefaultLocale"
+ message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+ errorLine1=" return String.format(" session: {id: %d, status: %s, result: %s, progress: %d}","
+ errorLine2=" ^">
<location
- file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java"
- line="314"
- column="56"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 29): `android.os.RemoteException#rethrowFromSystemServer`"
- errorLine1=" e.rethrowFromSystemServer();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaCommunicationManager.java"
- line="110"
- column="15"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 29): `android.os.Parcel#writeParcelableCreator`"
- errorLine1=" dest.writeParcelableCreator((Parcelable) parcelable);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParceledListSlice.java"
- line="77"
- column="14"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 29): `android.os.Parcel#readParcelableCreator`"
- errorLine1=" return from.readParcelableCreator(loader);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParceledListSlice.java"
- line="82"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.TrackData#mediaFormat`"
- errorLine1=" this.mediaFormat = mediaFormat;"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="273"
- column="13"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.TrackData#drmInitData`"
- errorLine1=" this.drmInitData = drmInitData;"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="274"
- column="13"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" this.timeMicros = timeMicros;"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="295"
- column="13"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" this.position = position;"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="296"
- column="13"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" return "[timeMicros=" + timeMicros + ", position=" + position + "]";"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="302"
- column="66"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" return "[timeMicros=" + timeMicros + ", position=" + position + "]";"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="302"
- column="37"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level R (current min is 29): `android.media.MediaParser.SeekPoint`"
- errorLine1=" SeekPoint other = (SeekPoint) obj;"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="313"
- column="32"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" return timeMicros == other.timeMicros && position == other.position;"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="314"
- column="54"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" return timeMicros == other.timeMicros && position == other.position;"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="314"
- column="66"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" return timeMicros == other.timeMicros && position == other.position;"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="314"
+ file="frameworks/base/apex/media/framework/java/android/media/MediaTranscodingManager.java"
+ line="1651"
column="20"/>
</issue>
<issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" return timeMicros == other.timeMicros && position == other.position;"
- errorLine2=" ~~~~~~~~~~~~~~~~">
+ id="ParcelClassLoader"
+ message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead."
+ errorLine1=" Bundle out = parcel.readBundle(null);"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
<location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="314"
- column="34"/>
+ file="frameworks/base/apex/media/framework/java/android/media/MediaSession2.java"
+ line="303"
+ column="33"/>
</issue>
<issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" int result = (int) timeMicros;"
- errorLine2=" ~~~~~~~~~~">
+ id="ParcelClassLoader"
+ message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead."
+ errorLine1=" mCustomExtras = in.readBundle();"
+ errorLine2=" ~~~~~~~~~~~~">
<location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="319"
- column="32"/>
+ file="frameworks/base/apex/media/framework/java/android/media/Session2Command.java"
+ line="104"
+ column="28"/>
</issue>
<issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" result = 31 * result + (int) position;"
- errorLine2=" ~~~~~~~~">
+ id="ParcelClassLoader"
+ message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead."
+ errorLine1=" mSessionLink = in.readParcelable(null);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="320"
- column="42"/>
+ file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java"
+ line="141"
+ column="27"/>
</issue>
<issue
- id="NewApi"
- message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`"
- errorLine1=" public interface SeekableInputReader extends InputReader {"
- errorLine2=" ~~~~~~~~~~~">
+ id="ParcelClassLoader"
+ message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead."
+ errorLine1=" Bundle extras = in.readBundle();"
+ errorLine2=" ~~~~~~~~~~~~">
<location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="352"
- column="50"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 31 (current min is 29): `android.media.metrics.LogSessionId#LOG_SESSION_ID_NONE`"
- errorLine1=" @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1071"
- column="51"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast from `SeekableInputReader` to `InputReader` requires API level 30 (current min is 29)"
- errorLine1=" mExoDataReader.mInputReader = seekableInputReader;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1201"
- column="39"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" mPendingSeekPosition = seekPoint.position;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1287"
- column="36"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" mPendingSeekTimeMicros = seekPoint.timeMicros;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1288"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" mExtractor.seek(seekPoint.position, seekPoint.timeMicros);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1290"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" mExtractor.seek(seekPoint.position, seekPoint.timeMicros);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1290"
- column="49"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.DrmInitData.SchemeInitData#uuid`"
- errorLine1=" if (schemeInitData.uuid.equals(schemeUuid)) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1579"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`"
- errorLine1=" private static final class DataReaderAdapter implements InputReader {"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1872"
- column="61"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`"
- errorLine1=" private static final class ParsableByteArrayAdapter implements InputReader {"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1905"
- column="68"/>
+ file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java"
+ line="144"
+ column="28"/>
</issue>
</issues>
diff --git a/apex/media/service/Android.bp b/apex/media/service/Android.bp
index cf384ac..834e5cb 100644
--- a/apex/media/service/Android.bp
+++ b/apex/media/service/Android.bp
@@ -39,6 +39,7 @@
":service-media-s-sources",
],
libs: [
+ "androidx.annotation_annotation",
"updatable-media",
"modules-annotation-minsdk",
"modules-utils-build",
@@ -46,6 +47,9 @@
jarjar_rules: "jarjar_rules.txt",
sdk_version: "system_server_current",
min_sdk_version: "29", // TODO: We may need to bump this at some point.
+ lint: {
+ strict_updatability_linting: true,
+ },
apex_available: [
"com.android.media",
],
diff --git a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
index 7d47e25..4223fa6 100644
--- a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
+++ b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
@@ -46,6 +46,8 @@
import android.util.SparseIntArray;
import android.view.KeyEvent;
+import androidx.annotation.RequiresApi;
+
import com.android.internal.annotations.GuardedBy;
import com.android.modules.annotation.MinSdk;
import com.android.server.SystemService;
@@ -63,6 +65,7 @@
* @hide
*/
@MinSdk(Build.VERSION_CODES.S)
+@RequiresApi(Build.VERSION_CODES.S)
public class MediaCommunicationService extends SystemService {
private static final String TAG = "MediaCommunicationSrv";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/apex/media/service/java/com/android/server/media/SessionPriorityList.java b/apex/media/service/java/com/android/server/media/SessionPriorityList.java
index 47b14b6..8145861 100644
--- a/apex/media/service/java/com/android/server/media/SessionPriorityList.java
+++ b/apex/media/service/java/com/android/server/media/SessionPriorityList.java
@@ -18,9 +18,13 @@
import android.annotation.Nullable;
import android.media.Session2Token;
+import android.os.Build;
import android.util.Log;
+import androidx.annotation.RequiresApi;
+
import com.android.internal.annotations.GuardedBy;
+import com.android.modules.annotation.MinSdk;
import com.android.server.media.MediaCommunicationService.Session2Record;
import java.util.ArrayList;
@@ -33,6 +37,8 @@
* Higher priority session has more chance to be selected as media button session,
* which receives the media button events.
*/
+@MinSdk(Build.VERSION_CODES.S)
+@RequiresApi(Build.VERSION_CODES.S)
class SessionPriorityList {
private static final String TAG = "SessionPriorityList";
private final Object mLock = new Object();
diff --git a/apex/media/service/lint-baseline.xml b/apex/media/service/lint-baseline.xml
index 05ce17c..def6baf 100644
--- a/apex/media/service/lint-baseline.xml
+++ b/apex/media/service/lint-baseline.xml
@@ -1,37 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
-
- <issue
- id="NewApi"
- message="Call requires API level S (current min is 29): `MediaParceledListSlice`"
- errorLine1=" new MediaParceledListSlice<>(getSession2TokensLocked(ALL.getIdentifier()));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/service/java/com/android/server/media/MediaCommunicationService.java"
- line="242"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level S (current min is 29): `MediaParceledListSlice`"
- errorLine1=" userSession2Tokens = new MediaParceledListSlice<>(getSession2TokensLocked(userId));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/service/java/com/android/server/media/MediaCommunicationService.java"
- line="243"
- column="34"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level S (current min is 29): `MediaParceledListSlice`"
- errorLine1=" MediaParceledListSlice parceledListSlice = new MediaParceledListSlice<>(result);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/service/java/com/android/server/media/MediaCommunicationService.java"
- line="386"
- column="60"/>
- </issue>
+<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev">
</issues>
diff --git a/api/Android.bp b/api/Android.bp
index 3075d38..d57f5db 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -116,7 +116,6 @@
"framework-graphics",
"framework-media",
"framework-mediaprovider",
- "framework-nearby",
"framework-permission",
"framework-permission-s",
"framework-scheduling",
diff --git a/api/api.go b/api/api.go
index aa9e399e..17649e8 100644
--- a/api/api.go
+++ b/api/api.go
@@ -27,7 +27,6 @@
const art = "art.module.public.api"
const conscrypt = "conscrypt.module.public.api"
const i18n = "i18n.module.public.api"
-var modules_with_only_public_scope = []string{i18n, conscrypt}
// The intention behind this soong plugin is to generate a number of "merged"
// API-related modules that would otherwise require a large amount of very
@@ -149,8 +148,6 @@
// This produces the same annotations.zip as framework-doc-stubs, but by using
// outputs from individual modules instead of all the source code.
func createMergedAnnotations(ctx android.LoadHookContext, modules []string) {
- // Conscrypt and i18n currently do not enable annotations
- modules = removeAll(modules, []string{conscrypt, i18n})
props := genruleProps{}
props.Name = proptools.StringPtr("sdk-annotations.zip")
props.Tools = []string{"merge_annotation_zips", "soong_zip"}
@@ -195,11 +192,8 @@
func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
props := libraryProps{}
- modules_with_system_stubs := removeAll(modules, modules_with_only_public_scope)
props.Name = proptools.StringPtr("all-modules-system-stubs")
- props.Static_libs = append(
- transformArray(modules_with_only_public_scope, "", ".stubs"),
- transformArray(modules_with_system_stubs, "", ".stubs.system")...)
+ props.Static_libs = transformArray(modules, "", ".stubs.system")
props.Sdk_version = proptools.StringPtr("module_current")
props.Visibility = []string{"//frameworks/base"}
ctx.CreateModule(java.LibraryFactory, &props)
@@ -226,8 +220,6 @@
func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
var textFiles []MergedTxtDefinition
- // Two module libraries currently do not support @SystemApi so only have the public scope.
- bcpWithSystemApi := removeAll(bootclasspath, modules_with_only_public_scope)
tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
for i, f := range []string{"current.txt", "removed.txt"} {
@@ -241,14 +233,14 @@
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
BaseTxt: ":non-updatable-system-" + f,
- Modules: bcpWithSystemApi,
+ Modules: bootclasspath,
ModuleTag: "{.system" + tagSuffix[i],
Scope: "system",
})
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
BaseTxt: ":non-updatable-module-lib-" + f,
- Modules: bcpWithSystemApi,
+ Modules: bootclasspath,
ModuleTag: "{.module-lib" + tagSuffix[i],
Scope: "module-lib",
})
diff --git a/boot/Android.bp b/boot/Android.bp
index 8958d70..55ffe7c 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -80,10 +80,6 @@
module: "com.android.mediaprovider-bootclasspath-fragment",
},
{
- apex: "com.android.nearby",
- module: "com.android.nearby-bootclasspath-fragment",
- },
- {
apex: "com.android.os.statsd",
module: "com.android.os.statsd-bootclasspath-fragment",
},
diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index dd33fdf..c8d2e0e 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -280,7 +280,7 @@
// Lower privacy policy (less restrictive) wins.
report->set_privacy_policy(args.getPrivacyPolicy());
}
- report->set_all_sections(report->all_sections() | args.all());
+ report->set_all_sections(report->all_sections() || args.all());
for (int section: args.sections()) {
if (!has_section(*report, section)) {
report->add_section(section);
diff --git a/core/api/current.txt b/core/api/current.txt
index 922ad16..40f589d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -135,9 +135,6 @@
field public static final String READ_HOME_APP_SEARCH_DATA = "android.permission.READ_HOME_APP_SEARCH_DATA";
field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE";
field public static final String READ_LOGS = "android.permission.READ_LOGS";
- field public static final String READ_MEDIA_AUDIO = "android.permission.READ_MEDIA_AUDIO";
- field public static final String READ_MEDIA_IMAGE = "android.permission.READ_MEDIA_IMAGE";
- field public static final String READ_MEDIA_VIDEO = "android.permission.READ_MEDIA_VIDEO";
field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY";
field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
@@ -223,8 +220,6 @@
field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES";
field public static final String NOTIFICATIONS = "android.permission-group.NOTIFICATIONS";
field public static final String PHONE = "android.permission-group.PHONE";
- field public static final String READ_MEDIA_AURAL = "android.permission-group.READ_MEDIA_AURAL";
- field public static final String READ_MEDIA_VISUAL = "android.permission-group.READ_MEDIA_VISUAL";
field public static final String SENSORS = "android.permission-group.SENSORS";
field public static final String SMS = "android.permission-group.SMS";
field public static final String STORAGE = "android.permission-group.STORAGE";
@@ -331,6 +326,9 @@
field public static final int allowClearUserData = 16842757; // 0x1010005
field public static final int allowClickWhenDisabled = 16844312; // 0x1010618
field public static final int allowEmbedded = 16843765; // 0x10103f5
+ field public static final int allowGameAngleDriver;
+ field public static final int allowGameDownscaling;
+ field public static final int allowGameFpsOverride;
field public static final int allowNativeHeapPointerTagging = 16844306; // 0x1010612
field public static final int allowParallelSyncs = 16843570; // 0x1010332
field public static final int allowSingleTap = 16843353; // 0x1010259
@@ -734,6 +732,10 @@
field public static final int freezesText = 16843116; // 0x101016c
field public static final int fromAlpha = 16843210; // 0x10101ca
field public static final int fromDegrees = 16843187; // 0x10101b3
+ field public static final int fromExtendBottom;
+ field public static final int fromExtendLeft;
+ field public static final int fromExtendRight;
+ field public static final int fromExtendTop;
field public static final int fromId = 16843850; // 0x101044a
field public static final int fromScene = 16843741; // 0x10103dd
field public static final int fromXDelta = 16843206; // 0x10101c6
@@ -1436,10 +1438,12 @@
field public static final int summaryOn = 16843247; // 0x10101ef
field public static final int supportedTypes;
field public static final int supportsAssist = 16844016; // 0x10104f0
+ field public static final int supportsBatteryGameMode;
field public static final int supportsInlineSuggestions = 16844301; // 0x101060d
field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
field public static final int supportsLocalInteraction = 16844047; // 0x101050f
field public static final int supportsMultipleDisplays = 16844182; // 0x1010596
+ field public static final int supportsPerformanceGameMode;
field public static final int supportsPictureInPicture = 16844023; // 0x10104f7
field public static final int supportsRtl = 16843695; // 0x10103af
field public static final int supportsStylusHandwriting;
@@ -1579,6 +1583,10 @@
field public static final int titleTextStyle = 16843512; // 0x10102f8
field public static final int toAlpha = 16843211; // 0x10101cb
field public static final int toDegrees = 16843188; // 0x10101b4
+ field public static final int toExtendBottom;
+ field public static final int toExtendLeft;
+ field public static final int toExtendRight;
+ field public static final int toExtendTop;
field public static final int toId = 16843849; // 0x1010449
field public static final int toScene = 16843742; // 0x10103de
field public static final int toXDelta = 16843207; // 0x10101c7
@@ -3127,6 +3135,11 @@
field public static final int GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT = 13; // 0xd
field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
field public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15; // 0xf
+ field public static final int GLOBAL_ACTION_DPAD_CENTER = 20; // 0x14
+ field public static final int GLOBAL_ACTION_DPAD_DOWN = 17; // 0x11
+ field public static final int GLOBAL_ACTION_DPAD_LEFT = 18; // 0x12
+ field public static final int GLOBAL_ACTION_DPAD_RIGHT = 19; // 0x13
+ field public static final int GLOBAL_ACTION_DPAD_UP = 16; // 0x10
field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
field public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10; // 0xa
field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
@@ -7311,10 +7324,10 @@
method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String);
method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
method public CharSequence getDeviceOwnerLockScreenInfo();
- method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
- method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
- method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
- method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+ method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+ method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+ method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+ method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
method @NonNull public String getEnrollmentSpecificId();
method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
@@ -12225,7 +12238,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.SharedLibraryInfo> CREATOR;
field public static final int TYPE_BUILTIN = 0; // 0x0
field public static final int TYPE_DYNAMIC = 1; // 0x1
- field public static final int TYPE_SDK = 3; // 0x3
+ field public static final int TYPE_SDK_PACKAGE = 3; // 0x3
field public static final int TYPE_STATIC = 2; // 0x2
field public static final int VERSION_UNDEFINED = -1; // 0xffffffff
}
@@ -12953,6 +12966,10 @@
field @NonNull public static final android.os.Parcelable.Creator<android.database.CursorWindow> CREATOR;
}
+ public class CursorWindowAllocationException extends java.lang.RuntimeException {
+ ctor public CursorWindowAllocationException(@NonNull String);
+ }
+
public class CursorWrapper implements android.database.Cursor {
ctor public CursorWrapper(android.database.Cursor);
method public void close();
@@ -13984,6 +14001,7 @@
enum_constant @Deprecated public static final android.graphics.Bitmap.Config ARGB_4444;
enum_constant public static final android.graphics.Bitmap.Config ARGB_8888;
enum_constant public static final android.graphics.Bitmap.Config HARDWARE;
+ enum_constant public static final android.graphics.Bitmap.Config RGBA_1010102;
enum_constant public static final android.graphics.Bitmap.Config RGBA_F16;
enum_constant public static final android.graphics.Bitmap.Config RGB_565;
}
@@ -22723,6 +22741,7 @@
method public int describeContents();
method @Nullable public String getClientPackageName();
method public int getConnectionState();
+ method @NonNull public java.util.Set<java.lang.String> getDeduplicationIds();
method @Nullable public CharSequence getDescription();
method @Nullable public android.os.Bundle getExtras();
method @NonNull public java.util.List<java.lang.String> getFeatures();
@@ -22756,6 +22775,7 @@
method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures();
method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String);
method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setDeduplicationIds(@NonNull java.util.Set<java.lang.String>);
method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
@@ -23282,8 +23302,12 @@
public final class RouteDiscoveryPreference implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getAllowedPackages();
+ method @NonNull public java.util.List<java.lang.String> getDeduplicationPackageOrder();
method @NonNull public java.util.List<java.lang.String> getPreferredFeatures();
+ method @NonNull public java.util.List<java.lang.String> getRequiredFeatures();
method public boolean shouldPerformActiveScan();
+ method public boolean shouldRemoveDuplicates();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR;
}
@@ -23292,7 +23316,10 @@
ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean);
ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference);
method @NonNull public android.media.RouteDiscoveryPreference build();
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setAllowedPackages(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setDeduplicationPackageOrder(@NonNull java.util.List<java.lang.String>);
method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setRequiredFeatures(@NonNull java.util.List<java.lang.String>);
method @NonNull public android.media.RouteDiscoveryPreference.Builder setShouldPerformActiveScan(boolean);
}
@@ -31408,6 +31435,9 @@
method @Nullable public byte[] createByteArray();
method @Nullable public char[] createCharArray();
method @Nullable public double[] createDoubleArray();
+ method @Nullable public <T> T createFixedArray(@NonNull Class<T>, @NonNull int...);
+ method @Nullable public <T, S extends android.os.IInterface> T createFixedArray(@NonNull Class<T>, @NonNull java.util.function.Function<android.os.IBinder,S>, @NonNull int...);
+ method @Nullable public <T, S extends android.os.Parcelable> T createFixedArray(@NonNull Class<T>, @NonNull android.os.Parcelable.Creator<S>, @NonNull int...);
method @Nullable public float[] createFloatArray();
method @Nullable public int[] createIntArray();
method @Nullable public <T extends android.os.IInterface> T[] createInterfaceArray(@NonNull java.util.function.IntFunction<T[]>, @NonNull java.util.function.Function<android.os.IBinder,T>);
@@ -31449,6 +31479,9 @@
method public void readException();
method public void readException(int, String);
method public android.os.ParcelFileDescriptor readFileDescriptor();
+ method public <T> void readFixedArray(@NonNull T);
+ method public <T, S extends android.os.IInterface> void readFixedArray(@NonNull T, @NonNull java.util.function.Function<android.os.IBinder,S>);
+ method public <T, S extends android.os.Parcelable> void readFixedArray(@NonNull T, @NonNull android.os.Parcelable.Creator<S>);
method public float readFloat();
method public void readFloatArray(@NonNull float[]);
method @Deprecated @Nullable public java.util.HashMap readHashMap(@Nullable ClassLoader);
@@ -31492,6 +31525,7 @@
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[]);
@@ -31509,6 +31543,7 @@
method public void writeDoubleArray(@Nullable double[]);
method public void writeException(@NonNull Exception);
method public void writeFileDescriptor(@NonNull java.io.FileDescriptor);
+ method public <T> void writeFixedArray(@Nullable T, int, @NonNull int...);
method public void writeFloat(float);
method public void writeFloatArray(@Nullable float[]);
method public void writeInt(int);
@@ -31729,9 +31764,14 @@
method public void release();
method public void release(int);
method public void setReferenceCounted(boolean);
+ method public void setStateListener(@NonNull java.util.concurrent.Executor, @Nullable android.os.PowerManager.WakeLockStateListener);
method public void setWorkSource(android.os.WorkSource);
}
+ public static interface PowerManager.WakeLockStateListener {
+ method public void onStateChanged(boolean);
+ }
+
public class Process {
ctor public Process();
method public static final long getElapsedCpuTime();
@@ -37882,6 +37922,7 @@
method public abstract void onFillRequest(@NonNull android.service.autofill.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
method public abstract void onSaveRequest(@NonNull android.service.autofill.SaveRequest, @NonNull android.service.autofill.SaveCallback);
method public void onSavedDatasetsInfoRequest(@NonNull android.service.autofill.SavedDatasetsInfoCallback);
+ field public static final String EXTRA_FILL_RESPONSE = "android.service.autofill.extra.FILL_RESPONSE";
field public static final String SERVICE_INTERFACE = "android.service.autofill.AutofillService";
field public static final String SERVICE_META_DATA = "android.autofill";
}
@@ -38037,6 +38078,7 @@
public final class FillRequest implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.os.Bundle getClientState();
+ method @Nullable public android.content.IntentSender getDelayedFillIntentSender();
method @NonNull public java.util.List<android.service.autofill.FillContext> getFillContexts();
method public int getFlags();
method public int getId();
@@ -38051,6 +38093,7 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+ field public static final int FLAG_DELAY_FILL = 4; // 0x4
field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2
field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
}
@@ -41196,6 +41239,7 @@
field public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
field public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
field public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
+ field public static final String KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL = "is_opportunistic_subscription_bool";
field public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool";
field public static final String KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY = "lte_rsrq_thresholds_int_array";
field public static final String KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY = "lte_rssnr_thresholds_int_array";
@@ -41280,6 +41324,7 @@
field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
field public static final String KEY_SMDP_SERVER_ADDRESS_STRING = "smdp_server_address_string";
field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
+ field public static final String KEY_SUBSCRIPTION_GROUP_UUID_STRING = "subscription_group_uuid_string";
field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool";
field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL = "supports_device_to_device_communication_using_dtmf_bool";
field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL = "supports_device_to_device_communication_using_rtp_bool";
@@ -41323,6 +41368,7 @@
field public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = "wfc_emergency_address_carrier_app_string";
field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
+ field public static final String REMOVE_GROUP_UUID_STRING = "00000000-0000-0000-0000-000000000000";
field public static final int SERVICE_CLASS_NONE = 0; // 0x0
field public static final int SERVICE_CLASS_VOICE = 1; // 0x1
field public static final int USSD_OVER_CS_ONLY = 2; // 0x2
@@ -51643,6 +51689,7 @@
method public boolean isSelected();
method public boolean isShowingHintText();
method public boolean isTextEntryKey();
+ method public boolean isTextSelectable();
method public boolean isVisibleToUser();
method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int);
@@ -51706,6 +51753,7 @@
method public void setStateDescription(@Nullable CharSequence);
method public void setText(CharSequence);
method public void setTextEntryKey(boolean);
+ method public void setTextSelectable(boolean);
method public void setTextSelection(int, int);
method public void setTooltipText(@Nullable CharSequence);
method public void setTouchDelegateInfo(@NonNull android.view.accessibility.AccessibilityNodeInfo.TouchDelegateInfo);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 8ea1abc..6e7bc76 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -54,6 +54,21 @@
method public void onCanceled(@NonNull android.app.PendingIntent);
}
+ public class PropertyInvalidatedCache<Query, Result> {
+ ctor public PropertyInvalidatedCache(int, int, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>);
+ method public final void disableForCurrentProcess();
+ method public final void invalidateCache();
+ method public static void invalidateCache(int, @NonNull String);
+ method @Nullable public final Result query(@NonNull Query);
+ field public static final int MODULE_BLUETOOTH = 2; // 0x2
+ }
+
+ public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> {
+ ctor public PropertyInvalidatedCache.QueryHandler();
+ method @Nullable public abstract R apply(@NonNull Q);
+ method public boolean shouldBypassCache(@NonNull Q);
+ }
+
public class StatusBarManager {
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
}
@@ -63,9 +78,9 @@
package android.app.admin {
public class DevicePolicyManager {
- method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void acknowledgeNewUserDisclaimer();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void acknowledgeNewUserDisclaimer();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getLogoutUser();
- method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int logoutUser();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int logoutUser();
field public static final String ACTION_SHOW_NEW_USER_DISCLAIMER = "android.app.action.SHOW_NEW_USER_DISCLAIMER";
}
@@ -126,6 +141,7 @@
public abstract class PackageManager {
method @NonNull public String getPermissionControllerPackageName();
+ method @NonNull public String getSupplementalProcessPackageName();
}
}
@@ -446,6 +462,17 @@
}
+package android.net.netstats {
+
+ public class NetworkStatsDataMigrationUtils {
+ method @NonNull public static android.net.NetworkStatsCollection readPlatformCollection(@NonNull String, long) throws java.io.IOException;
+ field public static final String PREFIX_UID = "uid";
+ field public static final String PREFIX_UID_TAG = "uid_tag";
+ field public static final String PREFIX_XT = "xt";
+ }
+
+}
+
package android.os {
public final class BatteryStatsManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 171ee9a..1cba58c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -76,6 +76,7 @@
field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
+ field public static final String BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE = "android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE";
field public static final String BLUETOOTH_MAP = "android.permission.BLUETOOTH_MAP";
field public static final String BRICK = "android.permission.BRICK";
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
@@ -146,6 +147,7 @@
field public static final String KILL_UID = "android.permission.KILL_UID";
field public static final String LAUNCH_DEVICE_MANAGER_SETUP = "android.permission.LAUNCH_DEVICE_MANAGER_SETUP";
field public static final String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS";
+ field public static final String LOCATION_BYPASS = "android.permission.LOCATION_BYPASS";
field public static final String LOCK_DEVICE = "android.permission.LOCK_DEVICE";
field public static final String LOOP_RADIO = "android.permission.LOOP_RADIO";
field public static final String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY";
@@ -165,6 +167,7 @@
field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
+ field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY";
field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE";
field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION";
field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
@@ -189,6 +192,7 @@
field public static final String MANAGE_USB = "android.permission.MANAGE_USB";
field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS";
field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
+ field public static final String MANAGE_WALLPAPER_EFFECTS_GENERATION = "android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION";
field public static final String MANAGE_WEAK_ESCROW_TOKEN = "android.permission.MANAGE_WEAK_ESCROW_TOKEN";
field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE";
@@ -201,6 +205,7 @@
field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE";
+ field public static final String MODIFY_TOUCH_MODE_STATE = "android.permission.MODIFY_TOUCH_MODE_STATE";
field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE";
field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING";
@@ -258,6 +263,7 @@
field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
field public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
field public static final String REAL_GET_TASKS = "android.permission.REAL_GET_TASKS";
+ field public static final String RECEIVE_BLUETOOTH_MAP = "android.permission.RECEIVE_BLUETOOTH_MAP";
field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
@@ -302,6 +308,7 @@
field public static final String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
field public static final String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY";
field public static final String SET_SYSTEM_AUDIO_CAPTION = "android.permission.SET_SYSTEM_AUDIO_CAPTION";
+ field public static final String SET_UNRESTRICTED_KEEP_CLEAR_AREAS = "android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS";
field public static final String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
field public static final String SET_WALLPAPER_DIM_AMOUNT = "android.permission.SET_WALLPAPER_DIM_AMOUNT";
@@ -348,6 +355,7 @@
field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS";
field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
+ field public static final String WRITE_SMS = "android.permission.WRITE_SMS";
}
public static final class Manifest.permission_group {
@@ -902,6 +910,19 @@
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
}
@@ -1057,8 +1078,8 @@
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
- method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>);
- method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>, @NonNull java.lang.Object...);
+ method @Nullable public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>);
+ method @Nullable public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>, @NonNull java.lang.Object...);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState();
method public boolean isDeviceManaged();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
@@ -1182,6 +1203,15 @@
field public static final String WORK_PROFILE_PAUSED_TITLE = "MediaProvider.WORK_PROFILE_PAUSED_TITLE";
}
+ public static final class DevicePolicyResources.Strings.PermissionController {
+ field public static final String BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE = "PermissionController.BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE";
+ field public static final String BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE = "PermissionController.BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE";
+ field public static final String FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE = "PermissionController.FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE";
+ field public static final String HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE = "PermissionController.HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE";
+ field public static final String LOCATION_AUTO_GRANTED_MESSAGE = "PermissionController.LOCATION_AUTO_GRANTED_MESSAGE";
+ field public static final String WORK_PROFILE_DEFAULT_APPS_TITLE = "PermissionController.WORK_PROFILE_DEFAULT_APPS_TITLE";
+ }
+
public final class DevicePolicyStringResource implements android.os.Parcelable {
ctor public DevicePolicyStringResource(@NonNull android.content.Context, @NonNull String, @StringRes int);
method public int describeContents();
@@ -1194,6 +1224,7 @@
public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
method public boolean canDeviceOwnerGrantSensorsPermissions();
method public int describeContents();
+ method @NonNull public android.os.PersistableBundle getAdminExtras();
method @NonNull public android.content.ComponentName getDeviceAdminComponentName();
method public long getLocalTime();
method @Nullable public java.util.Locale getLocale();
@@ -1207,6 +1238,7 @@
public static final class FullyManagedDeviceProvisioningParams.Builder {
ctor public FullyManagedDeviceProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams build();
+ method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setAdminExtras(@NonNull android.os.PersistableBundle);
method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setCanDeviceOwnerGrantSensorsPermissions(boolean);
method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocalTime(long);
@@ -1217,6 +1249,7 @@
public final class ManagedProfileProvisioningParams implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.accounts.Account getAccountToMigrate();
+ method @NonNull public android.os.PersistableBundle getAdminExtras();
method @NonNull public String getOwnerName();
method @NonNull public android.content.ComponentName getProfileAdminComponentName();
method @Nullable public String getProfileName();
@@ -1231,6 +1264,7 @@
ctor public ManagedProfileProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
method @NonNull public android.app.admin.ManagedProfileProvisioningParams build();
method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAccountToMigrate(@Nullable android.accounts.Account);
+ method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAdminExtras(@NonNull android.os.PersistableBundle);
method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setKeepingAccountOnMigration(boolean);
method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setOrganizationOwnedProvisioning(boolean);
@@ -2535,6 +2569,107 @@
}
+package android.app.wallpapereffectsgeneration {
+
+ public final class CameraAttributes implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public float[] getAnchorPointInOutputUvSpace();
+ method @NonNull public float[] getAnchorPointInWorldSpace();
+ method public float getCameraOrbitPitchDegrees();
+ method public float getCameraOrbitYawDegrees();
+ method public float getDollyDistanceInWorldSpace();
+ method public float getFrustumFarInWorldSpace();
+ method public float getFrustumNearInWorldSpace();
+ method public float getVerticalFovDegrees();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.CameraAttributes> CREATOR;
+ }
+
+ public static final class CameraAttributes.Builder {
+ ctor public CameraAttributes.Builder(@NonNull float[], @NonNull float[]);
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes build();
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setCameraOrbitPitchDegrees(@FloatRange(from=-90.0F, to=90.0f) float);
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setCameraOrbitYawDegrees(@FloatRange(from=-180.0F, to=180.0f) float);
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setDollyDistanceInWorldSpace(float);
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setFrustumFarInWorldSpace(@FloatRange(from=0.0f) float);
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setFrustumNearInWorldSpace(@FloatRange(from=0.0f) float);
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setVerticalFovDegrees(@FloatRange(from=0.0f, to=180.0f, fromInclusive=false) float);
+ }
+
+ public final class CinematicEffectRequest implements android.os.Parcelable {
+ ctor public CinematicEffectRequest(@NonNull String, @NonNull android.graphics.Bitmap);
+ method public int describeContents();
+ method @NonNull public android.graphics.Bitmap getBitmap();
+ method @NonNull public String getTaskId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.CinematicEffectRequest> CREATOR;
+ }
+
+ public final class CinematicEffectResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.app.wallpapereffectsgeneration.CameraAttributes getEndKeyFrame();
+ method public int getImageContentType();
+ method @Nullable public android.app.wallpapereffectsgeneration.CameraAttributes getStartKeyFrame();
+ method public int getStatusCode();
+ method @NonNull public String getTaskId();
+ method @NonNull public java.util.List<android.app.wallpapereffectsgeneration.TexturedMesh> getTexturedMeshes();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CINEMATIC_EFFECT_STATUS_ERROR = 2; // 0x2
+ field public static final int CINEMATIC_EFFECT_STATUS_NOT_READY = 3; // 0x3
+ field public static final int CINEMATIC_EFFECT_STATUS_OK = 1; // 0x1
+ field public static final int CINEMATIC_EFFECT_STATUS_PENDING = 4; // 0x4
+ field public static final int CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS = 5; // 0x5
+ field public static final int CINEMATIC_EFFECT_STATUS_UNKNOWN = 0; // 0x0
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.CinematicEffectResponse> CREATOR;
+ field public static final int IMAGE_CONTENT_TYPE_LANDSCAPE = 2; // 0x2
+ field public static final int IMAGE_CONTENT_TYPE_OTHER = 3; // 0x3
+ field public static final int IMAGE_CONTENT_TYPE_PEOPLE_PORTRAIT = 1; // 0x1
+ field public static final int IMAGE_CONTENT_TYPE_UNKNOWN = 0; // 0x0
+ }
+
+ public static final class CinematicEffectResponse.Builder {
+ ctor public CinematicEffectResponse.Builder(int, @NonNull String);
+ method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse build();
+ method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse.Builder setEndKeyFrame(@Nullable android.app.wallpapereffectsgeneration.CameraAttributes);
+ method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse.Builder setImageContentType(int);
+ method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse.Builder setStartKeyFrame(@Nullable android.app.wallpapereffectsgeneration.CameraAttributes);
+ method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse.Builder setTexturedMeshes(@NonNull java.util.List<android.app.wallpapereffectsgeneration.TexturedMesh>);
+ }
+
+ public final class TexturedMesh implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.graphics.Bitmap getBitmap();
+ method @NonNull public int[] getIndices();
+ method @NonNull public int getIndicesLayoutType();
+ method @NonNull public float[] getVertices();
+ method @NonNull public int getVerticesLayoutType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.TexturedMesh> CREATOR;
+ field public static final int INDICES_LAYOUT_TRIANGLES = 1; // 0x1
+ field public static final int INDICES_LAYOUT_UNDEFINED = 0; // 0x0
+ field public static final int VERTICES_LAYOUT_POSITION3_UV2 = 1; // 0x1
+ field public static final int VERTICES_LAYOUT_UNDEFINED = 0; // 0x0
+ }
+
+ public static final class TexturedMesh.Builder {
+ ctor public TexturedMesh.Builder(@NonNull android.graphics.Bitmap);
+ method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh build();
+ method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh.Builder setIndices(@NonNull int[]);
+ method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh.Builder setIndicesLayoutType(int);
+ method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh.Builder setVertices(@NonNull float[]);
+ method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh.Builder setVerticesLayoutType(int);
+ }
+
+ public final class WallpaperEffectsGenerationManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION) public void generateCinematicEffect(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager.CinematicEffectListener);
+ }
+
+ public static interface WallpaperEffectsGenerationManager.CinematicEffectListener {
+ method public void onCinematicEffectGenerated(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectResponse);
+ }
+
+}
+
package android.apphibernation {
public class AppHibernationManager {
@@ -2610,6 +2745,8 @@
public final class VirtualDeviceParams implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public java.util.Set<android.content.ComponentName> getAllowedActivities();
+ method @Nullable public java.util.Set<android.content.ComponentName> getBlockedActivities();
method public int getLockState();
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -2621,6 +2758,8 @@
public static final class VirtualDeviceParams.Builder {
ctor public VirtualDeviceParams.Builder();
method @NonNull public android.companion.virtual.VirtualDeviceParams build();
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@Nullable java.util.Set<android.content.ComponentName>);
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@Nullable java.util.Set<android.content.ComponentName>);
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
}
@@ -2678,6 +2817,7 @@
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.content.Intent registerReceiverForAllUsers(@Nullable android.content.BroadcastReceiver, @NonNull android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler, int);
method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
+ method public void sendBroadcastMultiplePermissions(@NonNull android.content.Intent, @NonNull String[], @Nullable android.app.BroadcastOptions);
method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
field public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context";
@@ -2688,7 +2828,7 @@
field public static final String BATTERY_STATS_SERVICE = "batterystats";
field @Deprecated public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000
field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000
- field public static final String CLOUDSEARCH_SERVICE = "cloudsearch_service";
+ field public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
field public static final String CONTEXTHUB_SERVICE = "contexthub";
field public static final String ETHERNET_SERVICE = "ethernet";
@@ -2718,6 +2858,7 @@
field public static final String UI_TRANSLATION_SERVICE = "ui_translation";
field public static final String UWB_SERVICE = "uwb";
field public static final String VR_SERVICE = "vrmanager";
+ field public static final String WALLPAPER_EFFECTS_GENERATION_SERVICE = "wallpaper_effects_generation";
field public static final String WIFI_NL80211_SERVICE = "wifinl80211";
field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
@@ -2781,6 +2922,7 @@
field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
+ field public static final String ACTION_USER_SWITCHED = "android.intent.action.USER_SWITCHED";
field @RequiresPermission(android.Manifest.permission.START_VIEW_APP_FEATURES) public static final String ACTION_VIEW_APP_FEATURES = "android.intent.action.VIEW_APP_FEATURES";
field public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
field public static final String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS";
@@ -2803,7 +2945,9 @@
field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
field public static final String EXTRA_SHOWING_ATTRIBUTION = "android.intent.extra.SHOWING_ATTRIBUTION";
field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
+ field public static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle";
field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
+ field public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 16777216; // 0x1000000
field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000
field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION";
}
@@ -3686,6 +3830,7 @@
method public void sendKeyEvent(int, boolean);
method public void sendVendorCommand(int, byte[], boolean);
method public void setVendorCommandListener(@NonNull android.hardware.hdmi.HdmiControlManager.VendorCommandListener);
+ method public void setVendorCommandListener(@NonNull android.hardware.hdmi.HdmiControlManager.VendorCommandListener, int);
}
public static interface HdmiClient.OnDeviceSelectedListener {
@@ -5534,9 +5679,9 @@
ctor public LastLocationRequest.Builder();
ctor public LastLocationRequest.Builder(@NonNull android.location.LastLocationRequest);
method @NonNull public android.location.LastLocationRequest build();
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LastLocationRequest.Builder setAdasGnssBypass(boolean);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LastLocationRequest.Builder setAdasGnssBypass(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS) public android.location.LastLocationRequest.Builder setHiddenFromAppOps(boolean);
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LastLocationRequest.Builder setLocationSettingsIgnored(boolean);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LastLocationRequest.Builder setLocationSettingsIgnored(boolean);
}
public class Location implements android.os.Parcelable {
@@ -5565,7 +5710,7 @@
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.app.PendingIntent);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAdasGnssLocationEnabled(boolean);
+ method @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public void setAdasGnssLocationEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackage(@Nullable String);
method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackageEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle);
@@ -5598,7 +5743,7 @@
method @Deprecated @NonNull public android.location.LocationRequest setFastestInterval(long);
method @Deprecated public void setHideFromAppOps(boolean);
method @Deprecated @NonNull public android.location.LocationRequest setInterval(long);
- method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest setLocationSettingsIgnored(boolean);
+ method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LocationRequest setLocationSettingsIgnored(boolean);
method @Deprecated @NonNull public android.location.LocationRequest setLowPowerMode(boolean);
method @Deprecated @NonNull public android.location.LocationRequest setNumUpdates(int);
method @Deprecated @NonNull public android.location.LocationRequest setProvider(@NonNull String);
@@ -5614,9 +5759,9 @@
}
public static final class LocationRequest.Builder {
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest.Builder setAdasGnssBypass(boolean);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LocationRequest.Builder setAdasGnssBypass(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS) public android.location.LocationRequest.Builder setHiddenFromAppOps(boolean);
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest.Builder setLocationSettingsIgnored(boolean);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LocationRequest.Builder setLocationSettingsIgnored(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public android.location.LocationRequest.Builder setLowPower(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.LocationRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
}
@@ -5868,6 +6013,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterMuteAwaitConnectionCallback(@NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
method public void unregisterVolumeGroupCallback(@NonNull android.media.AudioManager.VolumeGroupCallback);
+ field public static final String ACTION_VOLUME_CHANGED = "android.media.VOLUME_CHANGED_ACTION";
field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
field public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 2; // 0x2
@@ -5876,7 +6022,11 @@
field public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; // 0x2
field public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; // 0x1
field public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; // 0x0
+ field public static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE";
+ field public static final String EXTRA_VOLUME_STREAM_VALUE = "android.media.EXTRA_VOLUME_STREAM_VALUE";
+ field public static final int FLAG_BLUETOOTH_ABS_VOLUME = 64; // 0x40
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int STREAM_ASSISTANT = 11; // 0xb
+ field public static final int STREAM_BLUETOOTH_SCO = 6; // 0x6
field public static final int SUCCESS = 0; // 0x0
}
@@ -8918,6 +9068,8 @@
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanResults(@NonNull android.os.WorkSource, int);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStarted(@NonNull android.os.WorkSource, boolean);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStopped(@NonNull android.os.WorkSource, boolean);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockAcquiredFromSource(@NonNull android.os.WorkSource);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockReleasedFromSource(@NonNull android.os.WorkSource);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportMobileRadioPowerState(boolean, int);
@@ -8983,6 +9135,7 @@
}
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;
}
@@ -10219,6 +10372,7 @@
method @Deprecated public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean);
method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, @Nullable String, boolean);
field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
+ field public static final String ACTION_BEDTIME_SETTINGS = "android.settings.BEDTIME_SETTINGS";
field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS";
field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
field public static final String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS = "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS";
@@ -10259,6 +10413,8 @@
}
public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
+ method public static int getIntForUser(@NonNull android.content.ContentResolver, @NonNull String, int, int);
+ method @Nullable public static String getStringForUser(@NonNull android.content.ContentResolver, @NonNull String, int);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, @Nullable String, boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
field @Deprecated public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED = "accessibility_display_magnification_navbar_enabled";
@@ -10610,6 +10766,8 @@
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onCancelAttentionCheck(@NonNull android.service.attention.AttentionService.AttentionCallback);
method public abstract void onCheckAttention(@NonNull android.service.attention.AttentionService.AttentionCallback);
+ method public void onStartProximityUpdates(@NonNull android.service.attention.AttentionService.ProximityCallback);
+ method public void onStopProximityUpdates();
field public static final int ATTENTION_FAILURE_CAMERA_PERMISSION_ABSENT = 6; // 0x6
field public static final int ATTENTION_FAILURE_CANCELLED = 3; // 0x3
field public static final int ATTENTION_FAILURE_PREEMPTED = 4; // 0x4
@@ -10617,6 +10775,7 @@
field public static final int ATTENTION_FAILURE_UNKNOWN = 2; // 0x2
field public static final int ATTENTION_SUCCESS_ABSENT = 0; // 0x0
field public static final int ATTENTION_SUCCESS_PRESENT = 1; // 0x1
+ field public static final double PROXIMITY_UNKNOWN = -1.0;
field public static final String SERVICE_INTERFACE = "android.service.attention.AttentionService";
}
@@ -10625,6 +10784,10 @@
method public void onSuccess(int, long);
}
+ public static final class AttentionService.ProximityCallback {
+ method public void onProximityUpdate(double);
+ }
+
}
package android.service.autofill {
@@ -11034,7 +11197,7 @@
public class GameService extends android.app.Service {
ctor public GameService();
- method public final void createGameSession(@IntRange(from=0) int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final void createGameSession(@IntRange(from=0) int);
method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
method public void onConnected();
method public void onDisconnected();
@@ -11048,7 +11211,8 @@
method public void onCreate();
method public void onDestroy();
method public void onGameTaskFocusChanged(boolean);
- method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public final boolean restartGame();
+ method public void onTransientSystemBarVisibilityFromRevealGestureChanged(boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final boolean restartGame();
method public void setTaskOverlayView(@NonNull android.view.View, @NonNull android.view.ViewGroup.LayoutParams);
method public void takeScreenshot(@NonNull java.util.concurrent.Executor, @NonNull android.service.games.GameSession.ScreenshotCallback);
}
@@ -11477,6 +11641,7 @@
method @Deprecated public final void grantTrust(CharSequence, long, boolean);
method public final void grantTrust(CharSequence, long, int);
method public final void isEscrowTokenActive(long, android.os.UserHandle);
+ method public final void lockUser();
method public final android.os.IBinder onBind(android.content.Intent);
method public boolean onConfigure(java.util.List<android.os.PersistableBundle>);
method public void onDeviceLocked();
@@ -11487,13 +11652,16 @@
method public void onEscrowTokenStateReceived(long, int);
method public void onTrustTimeout();
method public void onUnlockAttempt(boolean);
+ method public void onUserRequestedUnlock();
method public final void removeEscrowToken(long, android.os.UserHandle);
method public final void revokeTrust();
method public final void setManagingTrust(boolean);
method public final void showKeyguardErrorMessage(@NonNull CharSequence);
method public final void unlockUserWithToken(long, byte[], android.os.UserHandle);
field public static final int FLAG_GRANT_TRUST_DISMISS_KEYGUARD = 2; // 0x2
+ field public static final int FLAG_GRANT_TRUST_DISPLAY_MESSAGE = 8; // 0x8
field public static final int FLAG_GRANT_TRUST_INITIATED_BY_USER = 1; // 0x1
+ field public static final int FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE = 4; // 0x4
field public static final String SERVICE_INTERFACE = "android.service.trust.TrustAgentService";
field public static final int TOKEN_STATE_ACTIVE = 1; // 0x1
field public static final int TOKEN_STATE_INACTIVE = 0; // 0x0
@@ -11669,6 +11837,17 @@
}
+package android.service.wallpapereffectsgeneration {
+
+ public abstract class WallpaperEffectsGenerationService extends android.app.Service {
+ ctor public WallpaperEffectsGenerationService();
+ method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onGenerateCinematicEffect(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectRequest);
+ method public final void returnCinematicEffectResponse(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectResponse);
+ }
+
+}
+
package android.service.watchdog {
public abstract class ExplicitHealthCheckService extends android.app.Service {
@@ -11843,6 +12022,7 @@
public abstract class ConnectionService extends android.app.Service {
method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference);
+ method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.Connection onCreateUnknownConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest);
}
public abstract class InCallService extends android.app.Service {
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index dbb4274..e17a9bb 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -294,7 +294,6 @@
ServiceName: android.content.Context#CLOUDSEARCH_SERVICE:
- Inconsistent service value; expected `cloudsearch`, was `cloudsearch_service` (Note: Do not change the name of already released services, which will break tools using `adb shell dumpsys`. Instead add `@SuppressLint("ServiceName"))`
UserHandleName: android.app.search.SearchAction.Builder#setUserHandle(android.os.UserHandle):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 39e12f4..fea7396 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -278,6 +278,7 @@
}
public final class GameManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public boolean isAngleEnabled(@NonNull String);
method public void setGameServiceProvider(@Nullable String);
}
@@ -355,6 +356,32 @@
ctor public PictureInPictureUiState(boolean);
}
+ public class PropertyInvalidatedCache<Query, Result> {
+ ctor public PropertyInvalidatedCache(int, int, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>);
+ method @NonNull public static String createPropertyName(int, @NonNull String);
+ method public final void disableForCurrentProcess();
+ method public static void disableForTestMode();
+ method public final void disableInstance();
+ method public final void disableSystemWide();
+ method public final void forgetDisableLocal();
+ method public boolean getDisabledState();
+ method public final void invalidateCache();
+ method public static void invalidateCache(int, @NonNull String);
+ method public final boolean isDisabled();
+ method @Nullable public final Result query(@NonNull Query);
+ method public static void setTestMode(boolean);
+ method public void testPropertyName();
+ field public static final int MODULE_BLUETOOTH = 2; // 0x2
+ field public static final int MODULE_SYSTEM = 1; // 0x1
+ field public static final int MODULE_TEST = 0; // 0x0
+ }
+
+ public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> {
+ ctor public PropertyInvalidatedCache.QueryHandler();
+ method @Nullable public abstract R apply(@NonNull Q);
+ method public boolean shouldBypassCache(@NonNull Q);
+ }
+
public class StatusBarManager {
method public void cancelRequestAddTile(@NonNull String);
method public void clickNotification(@Nullable String, int, int, boolean);
@@ -462,6 +489,7 @@
method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs();
method public void forceUpdateUserSetupComplete(int);
method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages();
+ method public int getDeviceOwnerType(@NonNull android.content.ComponentName);
method @NonNull public java.util.Set<java.lang.String> getDisallowedSystemApps(@NonNull android.content.ComponentName, int, @NonNull String);
method public long getLastBugReportRequestTime();
method public long getLastNetworkLogRetrievalTime();
@@ -470,6 +498,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public java.util.Set<java.lang.String> getPolicyExemptApps();
method public boolean isCurrentInputMethodSetByOwner();
method public boolean isFactoryResetProtectionPolicySupported();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isNewUserDisclaimerAcknowledged();
method @RequiresPermission(anyOf={android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName);
method @NonNull public static String operationSafetyReasonToString(int);
method @NonNull public static String operationToString(int);
@@ -477,6 +506,7 @@
method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, @Nullable String, int);
+ method public void setDeviceOwnerType(@NonNull android.content.ComponentName, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int);
field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
@@ -772,6 +802,7 @@
method @NonNull public String getPermissionControllerPackageName();
method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
method @NonNull public abstract String getSharedSystemSharedLibraryPackageName();
+ method @NonNull public String getSupplementalProcessPackageName();
method @Nullable public String getSystemTextClassifierPackageName();
method @Nullable public String getWellbeingPackageName();
method public void holdLock(android.os.IBinder, int);
@@ -1092,7 +1123,7 @@
package android.hardware.devicestate {
public final class DeviceStateManager {
- method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest);
+ method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelStateRequest();
method @NonNull public int[] getSupportedStates();
method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
@@ -1710,8 +1741,11 @@
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 {
@@ -2710,6 +2744,7 @@
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.bp b/core/java/Android.bp
index 8234f03..5649c5f 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -128,6 +128,7 @@
"android/os/IThermalStatusListener.aidl",
"android/os/IThermalService.aidl",
"android/os/IPowerManager.aidl",
+ "android/os/IWakeLockCallback.aidl",
],
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 6b0aef8..42d2d28 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -571,6 +571,31 @@
*/
public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15;
+ /**
+ * Action to trigger dpad up keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_UP = 16;
+
+ /**
+ * Action to trigger dpad down keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_DOWN = 17;
+
+ /**
+ * Action to trigger dpad left keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_LEFT = 18;
+
+ /**
+ * Action to trigger dpad right keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_RIGHT = 19;
+
+ /**
+ * Action to trigger dpad center keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_CENTER = 20;
+
private static final String LOG_TAG = "AccessibilityService";
/**
@@ -2328,10 +2353,16 @@
* @param action The action to perform.
* @return Whether the action was successfully performed.
*
+ * Perform actions using ids like the id constants referenced below:
* @see #GLOBAL_ACTION_BACK
* @see #GLOBAL_ACTION_HOME
* @see #GLOBAL_ACTION_NOTIFICATIONS
* @see #GLOBAL_ACTION_RECENTS
+ * @see #GLOBAL_ACTION_DPAD_UP
+ * @see #GLOBAL_ACTION_DPAD_DOWN
+ * @see #GLOBAL_ACTION_DPAD_LEFT
+ * @see #GLOBAL_ACTION_DPAD_RIGHT
+ * @see #GLOBAL_ACTION_DPAD_CENTER
*/
public final boolean performGlobalAction(int action) {
IAccessibilityServiceConnection connection =
diff --git a/core/java/android/accessibilityservice/TouchInteractionController.java b/core/java/android/accessibilityservice/TouchInteractionController.java
index bb2b8d4..735df80 100644
--- a/core/java/android/accessibilityservice/TouchInteractionController.java
+++ b/core/java/android/accessibilityservice/TouchInteractionController.java
@@ -376,7 +376,7 @@
throw new IllegalStateException(
"State transitions are not allowed without first adding a callback.");
}
- if (mState != STATE_TOUCH_INTERACTING) {
+ if (mState != STATE_TOUCH_INTERACTING && mState != STATE_DRAGGING) {
throw new IllegalStateException(
"State transitions are not allowed in " + stateToString(mState));
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3b2176e..3289304 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -200,6 +200,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
+import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SomeArgs;
@@ -1004,6 +1005,11 @@
RemoteCallback finishCallback;
}
+ static final class DumpResourcesData {
+ public ParcelFileDescriptor fd;
+ public RemoteCallback finishCallback;
+ }
+
static final class UpdateCompatibilityData {
String pkg;
CompatibilityInfo info;
@@ -1315,6 +1321,20 @@
sendMessage(H.SCHEDULE_CRASH, args, typeId);
}
+ @Override
+ public void dumpResources(ParcelFileDescriptor fd, RemoteCallback callback) {
+ DumpResourcesData data = new DumpResourcesData();
+ try {
+ data.fd = fd.dup();
+ data.finishCallback = callback;
+ sendMessage(H.DUMP_RESOURCES, data, 0, 0, false /*async*/);
+ } catch (IOException e) {
+ Slog.w(TAG, "dumpResources failed", e);
+ } finally {
+ IoUtils.closeQuietly(fd);
+ }
+ }
+
public void dumpActivity(ParcelFileDescriptor pfd, IBinder activitytoken,
String prefix, String[] args) {
DumpComponentInfo data = new DumpComponentInfo();
@@ -2038,6 +2058,7 @@
public static final int UPDATE_UI_TRANSLATION_STATE = 163;
public static final int SET_CONTENT_CAPTURE_OPTIONS_CALLBACK = 164;
public static final int DUMP_GFXINFO = 165;
+ public static final int DUMP_RESOURCES = 166;
public static final int INSTRUMENT_WITHOUT_RESTART = 170;
public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
@@ -2091,6 +2112,7 @@
case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART";
case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
return "FINISH_INSTRUMENTATION_WITHOUT_RESTART";
+ case DUMP_RESOURCES: return "DUMP_RESOURCES";
}
}
return Integer.toString(code);
@@ -2206,6 +2228,9 @@
case DUMP_HEAP:
handleDumpHeap((DumpHeapData) msg.obj);
break;
+ case DUMP_RESOURCES:
+ handleDumpResources((DumpResourcesData) msg.obj);
+ break;
case DUMP_ACTIVITY:
handleDumpActivity((DumpComponentInfo)msg.obj);
break;
@@ -4584,6 +4609,23 @@
}
}
+ private void handleDumpResources(DumpResourcesData info) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ try {
+ PrintWriter pw = new FastPrintWriter(new FileOutputStream(
+ info.fd.getFileDescriptor()));
+
+ Resources.dumpHistory(pw, "");
+ pw.flush();
+ if (info.finishCallback != null) {
+ info.finishCallback.sendResult(null);
+ }
+ } finally {
+ IoUtils.closeQuietly(info.fd);
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
private void handleDumpActivity(DumpComponentInfo info) {
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
@@ -7812,6 +7854,8 @@
MediaFrameworkPlatformInitializer.setMediaServiceManager(new MediaServiceManager());
MediaFrameworkInitializer.setMediaServiceManager(new MediaServiceManager());
BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
+ BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
+ BinderCallsStats.startForBluetooth(context); });
}
private void purgePendingResources() {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0d1bc05..fdf37f6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2363,11 +2363,11 @@
Manifest.permission.USE_BIOMETRIC,
Manifest.permission.ACTIVITY_RECOGNITION,
Manifest.permission.SMS_FINANCIAL_TRANSACTIONS,
- Manifest.permission.READ_MEDIA_AUDIO,
+ null,
null, // no permission for OP_WRITE_MEDIA_AUDIO
- Manifest.permission.READ_MEDIA_VIDEO,
+ null,
null, // no permission for OP_WRITE_MEDIA_VIDEO
- Manifest.permission.READ_MEDIA_IMAGE,
+ null,
null, // no permission for OP_WRITE_MEDIA_IMAGES
null, // no permission for OP_LEGACY_STORAGE
null, // no permission for OP_ACCESS_ACCESSIBILITY
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 7b55b6c..20ffa25 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -16,6 +16,11 @@
package android.app;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_NOT_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawables.UNDEFINED;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON_BADGE;
import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256;
import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
import static android.content.pm.Checksum.TYPE_WHOLE_MD5;
@@ -31,6 +36,7 @@
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.annotation.XmlRes;
+import android.app.admin.DevicePolicyManager;
import android.app.role.RoleManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -62,7 +68,6 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
@@ -74,7 +79,6 @@
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.dex.ArtManager;
-import android.content.pm.pkg.FrameworkPackageUserState;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -122,7 +126,6 @@
import libcore.util.EmptyArray;
-import java.io.File;
import java.lang.ref.WeakReference;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
@@ -172,6 +175,8 @@
private PackageInstaller mInstaller;
@GuardedBy("mLock")
private ArtManager mArtManager;
+ @GuardedBy("mLock")
+ private DevicePolicyManager mDevicePolicyManager;
@GuardedBy("mDelegates")
private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
@@ -188,6 +193,15 @@
}
}
+ DevicePolicyManager getDevicePolicyManager() {
+ synchronized (mLock) {
+ if (mDevicePolicyManager == null) {
+ mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+ }
+ return mDevicePolicyManager;
+ }
+ }
+
private PermissionManager getPermissionManager() {
synchronized (mLock) {
if (mPermissionManager == null) {
@@ -849,6 +863,18 @@
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public String getSupplementalProcessPackageName() {
+ try {
+ return mPM.getSupplementalProcessPackageName();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@Override
public boolean addPermission(PermissionInfo info) {
return getPermissionManager().addPermission(info, false);
@@ -1876,12 +1902,27 @@
if (!hasUserBadge(user.getIdentifier())) {
return icon;
}
+
+ final Drawable badgeForeground = getDevicePolicyManager().getDrawable(
+ getUpdatableUserIconBadgeId(user),
+ SOLID_COLORED,
+ () -> getDefaultUserIconBadge(user));
+
Drawable badge = new LauncherIcons(mContext).getBadgeDrawable(
- getUserManager().getUserIconBadgeResId(user.getIdentifier()),
+ badgeForeground,
getUserBadgeColor(user, false));
return getBadgedDrawable(icon, badge, null, true);
}
+ private String getUpdatableUserIconBadgeId(UserHandle user) {
+ return getUserManager().isManagedProfile(user.getIdentifier())
+ ? WORK_PROFILE_ICON_BADGE : UNDEFINED;
+ }
+
+ private Drawable getDefaultUserIconBadge(UserHandle user) {
+ return mContext.getDrawable(getUserManager().getUserIconBadgeResId(user.getIdentifier()));
+ }
+
@Override
public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user,
Rect badgeLocation, int badgeDensity) {
@@ -1913,13 +1954,28 @@
if (badgeColor == null) {
return null;
}
- Drawable badgeForeground = getDrawableForDensity(
- getUserManager().getUserBadgeResId(user.getIdentifier()), density);
+
+ final Drawable badgeForeground = getDevicePolicyManager().getDrawableForDensity(
+ getUpdatableUserBadgeId(user),
+ SOLID_COLORED,
+ density,
+ () -> getDefaultUserBadgeForDensity(user, density));
+
badgeForeground.setTint(getUserBadgeColor(user, false));
Drawable badge = new LayerDrawable(new Drawable[] {badgeColor, badgeForeground });
return badge;
}
+ private String getUpdatableUserBadgeId(UserHandle user) {
+ return getUserManager().isManagedProfile(user.getIdentifier())
+ ? WORK_PROFILE_ICON : UNDEFINED;
+ }
+
+ private Drawable getDefaultUserBadgeForDensity(UserHandle user, int density) {
+ return getDrawableForDensity(
+ getUserManager().getUserBadgeResId(user.getIdentifier()), density);
+ }
+
/**
* Returns the badge color based on whether device has dark theme enabled or not.
*/
@@ -1928,14 +1984,24 @@
if (!hasUserBadge(user.getIdentifier())) {
return null;
}
- Drawable badge = getDrawableForDensity(
- getUserManager().getUserBadgeNoBackgroundResId(user.getIdentifier()), density);
+
+ final Drawable badge = getDevicePolicyManager().getDrawableForDensity(
+ getUpdatableUserBadgeId(user),
+ SOLID_NOT_COLORED,
+ density,
+ () -> getDefaultUserBadgeNoBackgroundForDensity(user, density));
+
if (badge != null) {
badge.setTint(getUserBadgeColor(user, true));
}
return badge;
}
+ private Drawable getDefaultUserBadgeNoBackgroundForDensity(UserHandle user, int density) {
+ return getDrawableForDensity(
+ getUserManager().getUserBadgeNoBackgroundResId(user.getIdentifier()), density);
+ }
+
private Drawable getDrawableForDensity(int drawableId, int density) {
if (density <= 0) {
density = mContext.getResources().getDisplayMetrics().densityDpi;
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
index db58c21..7845b6a 100644
--- a/core/java/android/app/AsyncNotedAppOp.java
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -37,7 +37,8 @@
@Immutable
@DataClass(genEqualsHashCode = true,
genAidl = true,
- genHiddenConstructor = true)
+ genHiddenConstructor = true,
+ genToString = true)
// - We don't expose the opCode, but rather the public name of the op, hence use a non-standard
// getter
@DataClass.Suppress({"getOpCode"})
@@ -70,9 +71,13 @@
Preconditions.checkArgumentInRange(mOpCode, 0, AppOpsManager._NUM_OP - 1, "opCode");
}
+ private String opCodeToString() {
+ return getOp();
+ }
- // Code below generated by codegen v1.0.20.
+
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -160,6 +165,21 @@
@Override
@DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "AsyncNotedAppOp { " +
+ "opCode = " + opCodeToString() + ", " +
+ "notingUid = " + mNotingUid + ", " +
+ "attributionTag = " + mAttributionTag + ", " +
+ "message = " + mMessage + ", " +
+ "time = " + mTime +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(AsyncNotedAppOp other) { ... }
@@ -261,10 +281,10 @@
};
@DataClass.Generated(
- time = 1604456255752L,
- codegenVersion = "1.0.20",
+ time = 1643320606160L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
- inputSignatures = "private final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.IntRange int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.CurrentTimeMillisLong long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nprivate void onConstructed()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+ inputSignatures = "private final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.IntRange int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.CurrentTimeMillisLong long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nprivate void onConstructed()\nprivate java.lang.String opCodeToString()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f3315a8..8181a74 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1997,7 +1997,8 @@
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
String instanceName, Handler handler, Executor executor, UserHandle user) {
- // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser.
+ // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser and
+ // ActivityManagerService.LocalService.startAndBindSupplementalProcessService
IServiceConnection sd;
if (conn == null) {
throw new IllegalArgumentException("connection is null");
@@ -2023,10 +2024,10 @@
flags |= BIND_WAIVE_PRIORITY;
}
service.prepareToLeaveProcess(this);
- int res = ActivityManager.getService().bindIsolatedService(
- mMainThread.getApplicationThread(), getActivityToken(), service,
- service.resolveTypeIfNeeded(getContentResolver()),
- sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
+ int res = ActivityManager.getService().bindServiceInstance(
+ mMainThread.getApplicationThread(), getActivityToken(), service,
+ service.resolveTypeIfNeeded(getContentResolver()),
+ sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
if (res < 0) {
throw new SecurityException(
"Not allowed to bind to service " + service);
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 289b348..040399e 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -181,14 +181,18 @@
/**
* Returns if ANGLE is enabled for a given package and user ID.
* <p>
+ * ANGLE (Almost Native Graphics Layer Engine) can translate OpenGL ES commands to Vulkan
+ * commands. Enabling ANGLE may improve the performance and/or reduce the power consumption of
+ * applications.
* The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
*
* @hide
*/
+ @TestApi
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
- public @GameMode boolean getAngleEnabled(@NonNull String packageName) {
+ public @GameMode boolean isAngleEnabled(@NonNull String packageName) {
try {
- return mService.getAngleEnabled(packageName, mContext.getUserId());
+ return mService.isAngleEnabled(packageName, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 82c419a..e4ef12c 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -169,7 +169,7 @@
int bindService(in IApplicationThread caller, in IBinder token, in Intent service,
in String resolvedType, in IServiceConnection connection, int flags,
in String callingPackage, int userId);
- int bindIsolatedService(in IApplicationThread caller, in IBinder token, in Intent service,
+ int bindServiceInstance(in IApplicationThread caller, in IBinder token, in Intent service,
in String resolvedType, in IServiceConnection connection, int flags,
in String instanceName, in String callingPackage, int userId);
void updateServiceGroup(in IServiceConnection connection, int group, int importance);
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 1714229..77657d5 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -113,6 +113,7 @@
in ParcelFileDescriptor fd, in RemoteCallback finishCallback);
void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
in String[] args);
+ void dumpResources(in ParcelFileDescriptor fd, in RemoteCallback finishCallback);
void clearDnsCache();
void updateHttpProxy();
void setCoreSettings(in Bundle coreSettings);
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 3ea07676..7035ac0 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -26,7 +26,7 @@
int getGameMode(String packageName, int userId);
void setGameMode(String packageName, int gameMode, int userId);
int[] getAvailableGameModes(String packageName);
- boolean getAngleEnabled(String packageName, int userId);
+ boolean isAngleEnabled(String packageName, int userId);
void setGameState(String packageName, in GameState gameState, int userId);
GameModeInfo getGameModeInfo(String packageName, int userId);
void setGameServiceProvider(String packageName);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 14afd0f..5d1f4df 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -746,7 +746,7 @@
if (!hasPermission(Manifest.permission.SET_INITIAL_LOCK)) {
throw new SecurityException("Requires SET_INITIAL_LOCK permission.");
}
- return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ return true;
}
private boolean hasPermission(String permission) {
@@ -814,6 +814,8 @@
/**
* Set the lockscreen password after validating against its expected complexity level.
*
+ * Below {@link android.os.Build.VERSION_CODES#S_V2}, this API will only work
+ * when {@link PackageManager.FEATURE_AUTOMOTIVE} is present.
* @param lockType - type of lock as specified in {@link LockTypes}
* @param password - password to validate; this has the same encoding
* as the output of String#getBytes
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 4e32e9a..56c725e 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -746,6 +746,10 @@
// default linker namespace.
continue;
}
+ if (info.isSdk()) {
+ // SDKs are not loaded automatically.
+ continue;
+ }
if (libsToLoadAfter.contains(info.getName())) {
if (DEBUG) {
Slog.v(ActivityThread.TAG,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d57c288..6a14772 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -17,6 +17,10 @@
package android.app;
import static android.annotation.Dimension.DP;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawables.UNDEFINED;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
import static android.graphics.drawable.Icon.TYPE_URI;
import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
@@ -39,6 +43,7 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.DevicePolicyManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -71,6 +76,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.text.BidiFormatter;
import android.text.SpannableStringBuilder;
@@ -5046,6 +5052,18 @@
}
// Note: This assumes that the current user can read the profile badge of the
// originating user.
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ return dpm.getDrawable(
+ getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION,
+ this::getDefaultProfileBadgeDrawable);
+ }
+
+ private String getUpdatableProfileBadgeId() {
+ return mContext.getSystemService(UserManager.class).isManagedProfile()
+ ? WORK_PROFILE_ICON : UNDEFINED;
+ }
+
+ private Drawable getDefaultProfileBadgeDrawable() {
return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
new UserHandle(mContext.getUserId()), 0);
}
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index ec8d989..715de14 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -16,7 +16,11 @@
package android.app;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -27,9 +31,10 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastPrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
@@ -104,17 +109,21 @@
* <pre>
* public class ActivityThread {
* ...
+ * private final PropertyInvalidatedCache.QueryHandler<Integer, Birthday> mBirthdayQuery =
+ * new PropertyInvalidatedCache.QueryHandler<Integer, Birthday>() {
+ * {@literal @}Override
+ * public Birthday apply(Integer) {
+ * return GetService("birthdayd").getUserBirthday(userId);
+ * }
+ * };
* private static final int BDAY_CACHE_MAX = 8; // Maximum birthdays to cache
* private static final String BDAY_CACHE_KEY = "cache_key.birthdayd";
* private final PropertyInvalidatedCache<Integer, Birthday%> mBirthdayCache = new
- * PropertyInvalidatedCache<Integer, Birthday%>(BDAY_CACHE_MAX, BDAY_CACHE_KEY) {
- * {@literal @}Override
- * protected Birthday recompute(Integer userId) {
- * return GetService("birthdayd").getUserBirthday(userId);
- * }
- * };
+ * PropertyInvalidatedCache<Integer, Birthday%>(
+ * BDAY_CACHE_MAX, MODULE_SYSTEM, "getUserBirthday", mBirthdayQuery);
+ *
* public void disableUserBirthdayCache() {
- * mBirthdayCache.disableLocal();
+ * mBirthdayCache.disableForCurrentProcess();
* }
* public void invalidateUserBirthdayCache() {
* mBirthdayCache.invalidateCache();
@@ -221,10 +230,124 @@
*
* @param <Query> The class used to index cache entries: must be hashable and comparable
* @param <Result> The class holding cache entries; use a boxed primitive if possible
- *
- * {@hide}
+ * @hide
*/
-public abstract class PropertyInvalidatedCache<Query, Result> {
+@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+@TestApi
+public class PropertyInvalidatedCache<Query, Result> {
+ /**
+ * This is a configuration class that customizes a cache instance.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static abstract class QueryHandler<Q,R> {
+ /**
+ * Compute a result given a query. The semantics are those of Functor.
+ */
+ public abstract @Nullable R apply(@NonNull Q query);
+
+ /**
+ * Return true if a query should not use the cache. The default implementation
+ * always uses the cache.
+ */
+ public boolean shouldBypassCache(@NonNull Q query) {
+ return false;
+ }
+ };
+
+ /**
+ * The system properties used by caches should be of the form <prefix>.<module>.<api>,
+ * where the prefix is "cache_key", the module is one of the constants below, and the
+ * api is any string. The ability to write the property (which happens during
+ * invalidation) depends on SELinux rules; these rules are defined against
+ * <prefix>.<module>. Therefore, the module chosen for a cache property must match
+ * the permissions granted to the processes that contain the corresponding caches.
+ * @hide
+ */
+ @IntDef(prefix = { "MODULE_" }, value = {
+ MODULE_TEST,
+ MODULE_SYSTEM,
+ MODULE_BLUETOOTH
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Module {}
+
+ /**
+ * The module used for unit tests and cts tests. It is expected that no process in
+ * the system has permissions to write properties with this module.
+ * @hide
+ */
+ @TestApi
+ public static final int MODULE_TEST = 0;
+
+ /**
+ * The module used for system server/framework caches. This is not visible outside
+ * the system processes.
+ * @hide
+ */
+ @TestApi
+ public static final int MODULE_SYSTEM = 1;
+
+ /**
+ * The module used for bluetooth caches.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static final int MODULE_BLUETOOTH = 2;
+
+ // A static array mapping module constants to strings.
+ private final static String[] sModuleNames =
+ { "test", "system_server", "bluetooth" };
+
+ /**
+ * Construct a system property that matches the rules described above. The module is
+ * one of the permitted values above. The API is a string that is a legal Java simple
+ * identifier. The api is modified to conform to the system property style guide by
+ * replacing every upper case letter with an underscore and the lower case equivalent.
+ * There is no requirement that the apiName be the name of an actual API.
+ *
+ * Be aware that SystemProperties has a maximum length which is private to the
+ * implementation. The current maximum is 92 characters. If this method creates a
+ * property name that is too long, SystemProperties.set() will fail without a good
+ * error message.
+ * @hide
+ */
+ @TestApi
+ public static @NonNull String createPropertyName(@Module int module, @NonNull String apiName) {
+ char[] api = apiName.toCharArray();
+ int upper = 0;
+ for (int i = 0; i < api.length; i++) {
+ if (Character.isUpperCase(api[i])) {
+ upper++;
+ }
+ }
+ char[] suffix = new char[api.length + upper];
+ int j = 0;
+ for (int i = 0; i < api.length; i++) {
+ if (Character.isJavaIdentifierPart(api[i])) {
+ if (Character.isUpperCase(api[i])) {
+ suffix[j++] = '_';
+ suffix[j++] = Character.toLowerCase(api[i]);
+ } else {
+ suffix[j++] = api[i];
+ }
+ } else {
+ throw new IllegalArgumentException("invalid api name");
+ }
+ }
+
+ String moduleName = null;
+ try {
+ moduleName = sModuleNames[module];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new IllegalArgumentException("invalid module " + module);
+ }
+
+ return "cache_key." + moduleName + "." + new String(suffix);
+ }
+
/**
* Reserved nonce values. Use isReservedNonce() to test for a reserved value. Note
* that all values cause the cache to be skipped.
@@ -335,6 +458,25 @@
*/
private final String mCacheName;
+ /**
+ * The function that computes a Result, given a Query. This function is called on a
+ * cache miss.
+ */
+ private QueryHandler<Query, Result> mComputer;
+
+ /**
+ * A default function that delegates to the deprecated recompute() method.
+ */
+ private static class DefaultComputer<Query, Result> extends QueryHandler<Query, Result> {
+ final PropertyInvalidatedCache<Query, Result> mCache;
+ DefaultComputer(PropertyInvalidatedCache<Query, Result> cache) {
+ mCache = cache;
+ }
+ public Result apply(Query query) {
+ return mCache.recompute(query);
+ }
+ }
+
@GuardedBy("mLock")
private final LinkedHashMap<Query, Result> mCache;
@@ -359,8 +501,13 @@
* property name. New clients should prefer the constructor that takes an explicit
* cache name.
*
+ * TODO(216112648): deprecate this as a public interface, in favor of the four-argument
+ * constructor.
+ *
* @param maxEntries Maximum number of entries to cache; LRU discard
* @param propertyName Name of the system property holding the cache invalidation nonce.
+ *
+ * @hide
*/
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
this(maxEntries, propertyName, propertyName);
@@ -369,32 +516,73 @@
/**
* Make a new property invalidated cache.
*
+ * TODO(216112648): deprecate this as a public interface, in favor of the four-argument
+ * constructor.
+ *
* @param maxEntries Maximum number of entries to cache; LRU discard
* @param propertyName Name of the system property holding the cache invalidation nonce
* @param cacheName Name of this cache in debug and dumpsys
+ * @hide
*/
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
@NonNull String cacheName) {
mPropertyName = propertyName;
mCacheName = cacheName;
mMaxEntries = maxEntries;
- mCache = new LinkedHashMap<Query, Result>(
+ mComputer = new DefaultComputer<>(this);
+ mCache = createMap();
+ registerCache();
+ }
+
+ /**
+ * Make a new property invalidated cache. The key is computed from the module and api
+ * parameters.
+ *
+ * @param maxEntries Maximum number of entries to cache; LRU discard
+ * @param module The module under which the cache key should be placed.
+ * @param api The api this cache front-ends. The api must be a Java identifier but
+ * need not be an actual api.
+ * @param cacheName Name of this cache in debug and dumpsys
+ * @param computer The code to compute values that are not in the cache.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public PropertyInvalidatedCache(int maxEntries, @Module int module, @NonNull String api,
+ @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
+ mPropertyName = createPropertyName(module, api);
+ mCacheName = cacheName;
+ mMaxEntries = maxEntries;
+ mComputer = computer;
+ mCache = createMap();
+ registerCache();
+ }
+
+ // Create a map. This should be called only from the constructor.
+ private LinkedHashMap<Query, Result> createMap() {
+ return new LinkedHashMap<Query, Result>(
2 /* start small */,
0.75f /* default load factor */,
true /* LRU access order */) {
+ @GuardedBy("mLock")
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
final int size = size();
if (size > mHighWaterMark) {
mHighWaterMark = size;
}
- if (size > maxEntries) {
+ if (size > mMaxEntries) {
mMissOverflow++;
return true;
}
return false;
}
- };
+ };
+ }
+
+ // Register the map in the global list. If the cache is disabled globally, disable it
+ // now.
+ private void registerCache() {
synchronized (sCorkLock) {
sCaches.put(this, null);
if (sDisabledKeys.contains(mCacheName)) {
@@ -418,8 +606,9 @@
/**
* Enable or disable testing. The testing property map is cleared every time this
* method is called.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public static void setTestMode(boolean mode) {
sTesting = mode;
synchronized (sTestingPropertyMap) {
@@ -431,13 +620,22 @@
* Enable testing the specific cache key. Only keys in the map are subject to testing.
* There is no method to stop testing a property name. Just disable the test mode.
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public static void testPropertyName(@NonNull String name) {
+ private static void testPropertyName(@NonNull String name) {
synchronized (sTestingPropertyMap) {
sTestingPropertyMap.put(name, (long) NONCE_UNSET);
}
}
+ /**
+ * Enable testing the specific cache key. Only keys in the map are subject to testing.
+ * There is no method to stop testing a property name. Just disable the test mode.
+ * @hide
+ */
+ @TestApi
+ public void testPropertyName() {
+ testPropertyName(mPropertyName);
+ }
+
// Read the system property associated with the current cache. This method uses the
// handle for faster reading.
private long getCurrentNonce() {
@@ -490,6 +688,9 @@
/**
* Forget all cached values.
+ * TODO(216112648) remove this as a public API. Clients should invalidate caches, not clear
+ * them.
+ * @hide
*/
public final void clear() {
synchronized (mLock) {
@@ -505,22 +706,29 @@
* Fetch a result from scratch in case it's not in the cache at all. Called unlocked: may
* block. If this function returns null, the result of the cache query is null. There is no
* "negative cache" in the query: we don't cache null results at all.
+ * TODO(216112648): deprecate this as a public interface, in favor of an instance of
+ * QueryHandler.
+ * @hide
*/
- public abstract @NonNull Result recompute(@NonNull Query query);
+ public Result recompute(@NonNull Query query) {
+ return mComputer.apply(query);
+ }
/**
* Return true if the query should bypass the cache. The default behavior is to
* always use the cache but the method can be overridden for a specific class.
+ * TODO(216112648): deprecate this as a public interface, in favor of an instance of
+ * QueryHandler.
+ * @hide
*/
public boolean bypass(@NonNull Query query) {
- return false;
+ return mComputer.shouldBypassCache(query);
}
/**
- * Determines if a pair of responses are considered equal. Used to determine whether a
- * cache is inadvertently returning stale results when VERIFY is set to true. Some
- * existing clients override this method, but it is now deprecated in favor of a valid
- * equals() method on the Result class.
+ * Determines if a pair of responses are considered equal. Used to determine whether
+ * a cache is inadvertently returning stale results when VERIFY is set to true.
+ * @hide
*/
public boolean resultEquals(Result cachedResult, Result fetchedResult) {
// If a service crashes and returns a null result, the cached value remains valid.
@@ -541,6 +749,7 @@
* the meantime (if the nonce has changed in the meantime, we drop the cache and try the
* whole query again), or 3) null, which causes the old value to be removed from the cache
* and null to be returned as the result of the cache query.
+ * @hide
*/
protected Result refresh(Result oldResult, Query query) {
return oldResult;
@@ -551,7 +760,7 @@
* testing. To disable a cache in normal code, use disableLocal().
* @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public final void disableInstance() {
synchronized (mLock) {
mDisabled = true;
@@ -580,9 +789,10 @@
* disabled remain disabled (the "disabled" setting is sticky). However, new caches
* with this name will not be disabled. It is not an error if the cache name is not
* found in the list of disabled caches.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public final void clearDisableLocal() {
+ @TestApi
+ public final void forgetDisableLocal() {
synchronized (sCorkLock) {
sDisabledKeys.remove(mCacheName);
}
@@ -592,25 +802,43 @@
* Disable this cache in the current process, and all other caches that use the same
* name. This does not affect caches that have a different name but use the same
* property.
+ * TODO(216112648) Remove this in favor of disableForCurrentProcess().
+ * @hide
*/
public final void disableLocal() {
+ disableForCurrentProcess();
+ }
+
+ /**
+ * Disable this cache in the current process, and all other caches that use the same
+ * name. This does not affect caches that have a different name but use the same
+ * property.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public final void disableForCurrentProcess() {
disableLocal(mCacheName);
}
/**
- * Return whether the cache is disabled in this process.
+ * Return whether a cache instance is disabled.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public final boolean isDisabledLocal() {
+ @TestApi
+ public final boolean isDisabled() {
return mDisabled || !sEnabled;
}
/**
* Get a value from the cache or recompute it.
+ * @hide
*/
- public @NonNull Result query(@NonNull Query query) {
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public final @Nullable Result query(@NonNull Query query) {
// Let access to mDisabled race: it's atomic anyway.
- long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED;
+ long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED;
if (bypass(query)) {
currentNonce = NONCE_BYPASS;
}
@@ -724,8 +952,9 @@
* When multiple caches share a single property value, using an instance method on one of
* the cache objects to invalidate all of the cache objects becomes confusing and you should
* just use the static version of this function.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public final void disableSystemWide() {
disableSystemWide(mPropertyName);
}
@@ -746,16 +975,33 @@
/**
* Non-static convenience version of invalidateCache() for situations in which only a single
* PropertyInvalidatedCache is keyed on a particular property value.
+ * @hide
*/
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
public final void invalidateCache() {
invalidateCache(mPropertyName);
}
/**
+ * Invalidate caches in all processes that are keyed for the module and api.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static void invalidateCache(@Module int module, @NonNull String api) {
+ invalidateCache(createPropertyName(module, api));
+ }
+
+ /**
* Invalidate PropertyInvalidatedCache caches in all processes that are keyed on
* {@var name}. This function is synchronous: caches are invalidated upon return.
*
+ * TODO(216112648) make this method private in favor of the two-argument (module, api)
+ * override.
+ *
* @param name Name of the cache-key property to invalidate
+ * @hide
*/
public static void invalidateCache(@NonNull String name) {
if (!sEnabled) {
@@ -824,6 +1070,7 @@
* corkInvalidations() and uncorkInvalidations() must be called in pairs.
*
* @param name Name of the cache-key property to cork
+ * @hide
*/
public static void corkInvalidations(@NonNull String name) {
if (!sEnabled) {
@@ -871,6 +1118,7 @@
* transitioning it to normal operation (unless explicitly disabled system-wide).
*
* @param name Name of the cache-key property to uncork
+ * @hide
*/
public static void uncorkInvalidations(@NonNull String name) {
if (!sEnabled) {
@@ -916,6 +1164,7 @@
* The auto-cork delay is configurable but it should not be too long. The purpose of
* the delay is to minimize the number of times a server writes to the system property
* when invalidating the cache. One write every 50ms does not hurt system performance.
+ * @hide
*/
public static final class AutoCorker {
public static final int DEFAULT_AUTO_CORK_DELAY_MS = 50;
@@ -1043,6 +1292,8 @@
* Return the query as a string, to be used in debug messages. New clients should not
* override this, but should instead add the necessary toString() method to the Query
* class.
+ * TODO(216112648) add a method in the QueryHandler and deprecate this API.
+ * @hide
*/
protected @NonNull String queryToString(@NonNull Query query) {
return Objects.toString(query);
@@ -1054,8 +1305,9 @@
* process does not have privileges to write SystemProperties. Once disabled it is not
* possible to re-enable caching in the current process. If a client wants to
* temporarily disable caching, use the corking mechanism.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public static void disableForTestMode() {
Log.d(TAG, "disabling all caches in the process");
sEnabled = false;
@@ -1064,10 +1316,11 @@
/**
* Report the disabled status of this cache instance. The return value does not
* reflect status of the property key.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public boolean getDisabledState() {
- return isDisabledLocal();
+ return isDisabled();
}
/**
@@ -1133,7 +1386,8 @@
}
/**
- * Dumps the contents of every cache in the process to the provided ParcelFileDescriptor.
+ * Dumps contents of every cache in the process to the provided ParcelFileDescriptor.
+ * @hide
*/
public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) {
ArrayList<PropertyInvalidatedCache> activeCaches;
@@ -1174,6 +1428,7 @@
/**
* Trim memory by clearing all the caches.
+ * @hide
*/
public static void onTrimMemory() {
for (PropertyInvalidatedCache pic : getActiveCaches()) {
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 56c301f..8fcb07f 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.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 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 @@
@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 @@
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 @@
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/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
index 7c0c08a..f156b30 100644
--- a/core/java/android/app/SyncNotedAppOp.java
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -40,7 +40,8 @@
@DataClass(
genEqualsHashCode = true,
genAidl = true,
- genConstructor = false
+ genConstructor = false,
+ genToString = true
)
@DataClass.Suppress({"getOpCode", "getOpMode"})
public final class SyncNotedAppOp implements Parcelable {
@@ -118,6 +119,10 @@
return mOpMode;
}
+ private String opCodeToString() {
+ return getOp();
+ }
+
// Code below generated by codegen v1.0.23.
@@ -153,6 +158,20 @@
@Override
@DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "SyncNotedAppOp { " +
+ "opMode = " + mOpMode + ", " +
+ "opCode = " + opCodeToString() + ", " +
+ "attributionTag = " + mAttributionTag + ", " +
+ "packageName = " + mPackageName +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(SyncNotedAppOp other) { ... }
@@ -245,10 +264,10 @@
};
@DataClass.Generated(
- time = 1619711733947L,
+ time = 1643320427700L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/app/SyncNotedAppOp.java",
- inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false)")
+ inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nprivate java.lang.String opCodeToString()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index bbdd705..79180cb 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -51,6 +51,8 @@
import android.app.usage.NetworkStatsManager;
import android.app.usage.StorageStatsManager;
import android.app.usage.UsageStatsManager;
+import android.app.wallpapereffectsgeneration.IWallpaperEffectsGenerationManager;
+import android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager;
import android.apphibernation.AppHibernationManager;
import android.appwidget.AppWidgetManager;
import android.bluetooth.BluetoothFrameworkInitializer;
@@ -1297,6 +1299,20 @@
}
});
+ registerService(Context.WALLPAPER_EFFECTS_GENERATION_SERVICE,
+ WallpaperEffectsGenerationManager.class,
+ new CachedServiceFetcher<WallpaperEffectsGenerationManager>() {
+ @Override
+ public WallpaperEffectsGenerationManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getService(
+ Context.WALLPAPER_EFFECTS_GENERATION_SERVICE);
+ return b == null ? null :
+ new WallpaperEffectsGenerationManager(
+ IWallpaperEffectsGenerationManager.Stub.asInterface(b));
+ }
+ });
+
registerService(Context.VR_SERVICE, VrManager.class, new CachedServiceFetcher<VrManager>() {
@Override
public VrManager createService(ContextImpl ctx) throws ServiceNotFoundException {
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 18f9379..3d2c03d 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -214,6 +214,12 @@
public boolean topActivityInSizeCompat;
/**
+ * Whether the direct top activity is eligible for letterbox education.
+ * @hide
+ */
+ public boolean topActivityEligibleForLetterboxEducation;
+
+ /**
* Whether this task is resizable. Unlike {@link #resizeMode} (which is what the top activity
* supports), this is what the system actually uses for resizability based on other policy and
* developer options.
@@ -398,7 +404,8 @@
/** @hide */
public boolean hasCompatUI() {
- return hasCameraCompatControl() || topActivityInSizeCompat;
+ return hasCameraCompatControl() || topActivityInSizeCompat
+ || topActivityEligibleForLetterboxEducation;
}
/**
@@ -460,6 +467,8 @@
return displayId == that.displayId
&& taskId == that.taskId
&& topActivityInSizeCompat == that.topActivityInSizeCompat
+ && topActivityEligibleForLetterboxEducation
+ == that.topActivityEligibleForLetterboxEducation
&& cameraCompatControlState == that.cameraCompatControlState
// Bounds are important if top activity has compat controls.
&& (!hasCompatUI() || configuration.windowConfiguration.getBounds()
@@ -507,6 +516,7 @@
isVisible = source.readBoolean();
isSleeping = source.readBoolean();
topActivityInSizeCompat = source.readBoolean();
+ topActivityEligibleForLetterboxEducation = source.readBoolean();
mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
displayAreaFeatureId = source.readInt();
cameraCompatControlState = source.readInt();
@@ -551,6 +561,7 @@
dest.writeBoolean(isVisible);
dest.writeBoolean(isSleeping);
dest.writeBoolean(topActivityInSizeCompat);
+ dest.writeBoolean(topActivityEligibleForLetterboxEducation);
dest.writeTypedObject(mTopActivityLocusId, flags);
dest.writeInt(displayAreaFeatureId);
dest.writeInt(cameraCompatControlState);
@@ -585,6 +596,8 @@
+ " isVisible=" + isVisible
+ " isSleeping=" + isSleeping
+ " topActivityInSizeCompat=" + topActivityInSizeCompat
+ + " topActivityEligibleForLetterboxEducation= "
+ + topActivityEligibleForLetterboxEducation
+ " locusId=" + mTopActivityLocusId
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " cameraCompatControlState="
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3960f4e..8326580 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -454,6 +454,52 @@
* <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
* </ul>
*
+ * <p>Once the device admin app is set as the device owner, the following APIs are available for
+ * managing polices on the device:
+ * <ul>
+ * <li>{@link #isDeviceManaged()}</li>
+ * <li>{@link #isUninstallBlocked(ComponentName, String)}</li>
+ * <li>{@link #setUninstallBlocked(ComponentName, String, boolean)}</li>
+ * <li>{@link #setUserControlDisabledPackages(ComponentName, List)}</li>
+ * <li>{@link #getUserControlDisabledPackages(ComponentName)}</li>
+ * <li>{@link #setOrganizationName(ComponentName, CharSequence)}</li>
+ * <li>{@link #setShortSupportMessage(ComponentName, CharSequence)}</li>
+ * <li>{@link #isBackupServiceEnabled(ComponentName)}</li>
+ * <li>{@link #setBackupServiceEnabled(ComponentName, boolean)}</li>
+ * <li>{@link #isLockTaskPermitted(String)}</li>
+ * <li>{@link #setLockTaskFeatures(ComponentName, int)}, where the following lock task features
+ * can be set (otherwise a {@link SecurityException} will be thrown):</li>
+ * <ul>
+ * <li>{@link #LOCK_TASK_FEATURE_SYSTEM_INFO}</li>
+ * <li>{@link #LOCK_TASK_FEATURE_KEYGUARD}</li>
+ * <li>{@link #LOCK_TASK_FEATURE_HOME}</li>
+ * <li>{@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}</li>
+ * <li>{@link #LOCK_TASK_FEATURE_NOTIFICATIONS}</li>
+ * </ul>
+ * <li>{@link #setLockTaskPackages(ComponentName, String[])}</li>
+ * <li>{@link #addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName)}</li>
+ * <li>{@link #clearPackagePersistentPreferredActivities(ComponentName, String)} </li>
+ * <li>{@link #wipeData(int)}</li>
+ * <li>{@link #isDeviceOwnerApp(String)}</li>
+ * <li>{@link #clearDeviceOwnerApp(String)}</li>
+ * <li>{@link #setPermissionGrantState(ComponentName, String, String, int)}, where
+ * {@link permission#READ_PHONE_STATE} is the <b>only</b> permission that can be
+ * {@link #PERMISSION_GRANT_STATE_GRANTED}, {@link #PERMISSION_GRANT_STATE_DENIED}, or
+ * {@link #PERMISSION_GRANT_STATE_DEFAULT} and can <b>only</b> be applied to the device admin
+ * app (otherwise a {@link SecurityException} will be thrown)</li>
+ * <li>{@link #addUserRestriction(ComponentName, String)}, where the following user restrictions
+ * are permitted (otherwise a {@link SecurityException} will be thrown):</li>
+ * <ul>
+ * <li>{@link UserManager#DISALLOW_ADD_USER}</li>
+ * <li>{@link UserManager#DISALLOW_DEBUGGING_FEATURES}</li>
+ * <li>{@link UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES}</li>
+ * <li>{@link UserManager#DISALLOW_SAFE_BOOT}</li>
+ * <li>{@link UserManager#DISALLOW_CONFIG_DATE_TIME}</li>
+ * <li>{@link UserManager#DISALLOW_OUTGOING_CALLS}</li>
+ * </ul>
+ * <li>{@link #clearUserRestriction(ComponentName, String)}</li>
+ * </ul>
+ *
* @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -587,7 +633,7 @@
* #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} to signal that an update
* to the role holder is required.
*
- * <p>This result code must be accompanied by {@link #EXTRA_ROLE_HOLDER_STATE}.
+ * <p>This result code can be accompanied by {@link #EXTRA_ROLE_HOLDER_STATE}.
*
* @hide
*/
@@ -595,15 +641,19 @@
public static final int RESULT_UPDATE_ROLE_HOLDER = 2;
/**
- * A {@link Bundle} extra which describes the state of the role holder at the time when it
- * returns {@link #RESULT_UPDATE_ROLE_HOLDER}.
+ * A {@link PersistableBundle} extra which the role holder can use to describe its own state
+ * when it returns {@link #RESULT_UPDATE_ROLE_HOLDER}.
*
- * <p>After the update completes, the role holder's {@link
- * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} or {@link
+ * <p>If {@link #RESULT_UPDATE_ROLE_HOLDER} was accompanied by this extra, after the update
+ * completes, the role holder's {@link #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} or {@link
* #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent will be relaunched,
* which will contain this extra. It is the role holder's responsibility to restore its
* state from this extra.
*
+ * <p>The content of this {@link PersistableBundle} is entirely up to the role holder. It
+ * should contain anything the role holder needs to restore its original state when it gets
+ * restarted.
+ *
* @hide
*/
@SystemApi
@@ -3506,7 +3556,8 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public void acknowledgeNewUserDisclaimer() {
if (mService != null) {
@@ -3519,6 +3570,25 @@
}
/**
+ * Checks whether the new managed user disclaimer was viewed by the current user.
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ @TestApi
+ public boolean isNewUserDisclaimerAcknowledged() {
+ if (mService != null) {
+ try {
+ return mService.isNewUserDisclaimerAcknowledged();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
* Return true if the given administrator component is currently active (enabled) in the system.
*
* @param admin The administrator component to check for.
@@ -10096,7 +10166,7 @@
* @hide
*/
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.CREATE_USERS})
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public @UserOperationResult int logoutUser() {
// TODO(b/214336184): add CTS test
@@ -14577,6 +14647,7 @@
*
* @hide
*/
+ @TestApi
public void setDeviceOwnerType(@NonNull ComponentName admin,
@DeviceOwnerType int deviceOwnerType) {
throwIfParentInstance("setDeviceOwnerType");
@@ -14600,6 +14671,7 @@
*
* @hide
*/
+ @TestApi
@DeviceOwnerType
public int getDeviceOwnerType(@NonNull ComponentName admin) {
throwIfParentInstance("getDeviceOwnerType");
@@ -14767,6 +14839,11 @@
* The device may not connect to networks that do not meet the minimum security level.
* If the current network does not meet the minimum security level set, it will be disconnected.
*
+ * The following shows the Wi-Fi security levels from the lowest to the highest security level:
+ * {@link #WIFI_SECURITY_OPEN}
+ * {@link #WIFI_SECURITY_PERSONAL}
+ * {@link #WIFI_SECURITY_ENTERPRISE_EAP}
+ * {@link #WIFI_SECURITY_ENTERPRISE_192}
*
* @param level minimum security level
* @throws SecurityException if the caller is not a device owner or a profile owner on
@@ -14943,8 +15020,8 @@
* <p>Also returns the drawable from {@code defaultDrawableLoader} if
* {@link DevicePolicyResources.Drawables#UNDEFINED} was passed.
*
- * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
- * {@link NullPointerException} is thrown.
+ * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+ * and the call to {@code defaultDrawableLoader} returned {@code null}.
*
* <p>This API uses the screen density returned from {@link Resources#getConfiguration()}, to
* set a different value use
@@ -14961,7 +15038,7 @@
* @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
* the provided params.
*/
- @NonNull
+ @Nullable
public Drawable getDrawable(
@NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
@NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -14977,8 +15054,8 @@
* {@link #getDrawable(String, String, Callable)}
* if an override was set for that specific source.
*
- * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
- * {@link NullPointerException} is thrown.
+ * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+ * and the call to {@code defaultDrawableLoader} returned {@code null}.
*
* <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
* notified when a resource has been updated.
@@ -14989,7 +15066,7 @@
* @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
* the provided params.
*/
- @NonNull
+ @Nullable
public Drawable getDrawable(
@NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
@NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -15032,8 +15109,8 @@
* Similar to {@link #getDrawable(String, String, Callable)}, but also accepts
* {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
*
- * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
- * {@link NullPointerException} is thrown.
+ * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+ * and the call to {@code defaultDrawableLoader} returned {@code null}.
*
* <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
* notified when a resource has been updated.
@@ -15046,7 +15123,7 @@
* @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
* the provided params.
*/
- @NonNull
+ @Nullable
public Drawable getDrawableForDensity(
@NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
@NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -15064,8 +15141,8 @@
* Similar to {@link #getDrawable(String, String, String, Callable)}, but also accepts
* {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
*
- * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
- * {@link NullPointerException} is thrown.
+ * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+ * and the call to {@code defaultDrawableLoader} returned {@code null}.
*
* <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
* notified when a resource has been updated.
@@ -15079,7 +15156,7 @@
* @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
* the provided params.
*/
- @NonNull
+ @Nullable
public Drawable getDrawableForDensity(
@NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
@NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -15118,7 +15195,7 @@
/**
* For each {@link DevicePolicyStringResource} item in {@code strings}, it updates the string
* resource for {@link DevicePolicyStringResource#getStringId()} to the string with ID
- * {@code callingPackageResourceId} (see {@link DevicePolicyResources.String}), meaning any
+ * {@code callingPackageResourceId} (see {@link DevicePolicyResources.Strings}), meaning any
* system UI surface calling {@link #getString} with {@code stringId} will get
* the new resource after this API is called.
*
@@ -15154,7 +15231,7 @@
/**
* Removes the updated strings for the list of {@code stringIds} (see
- * {@link DevicePolicyResources.String}) that was previously set by calling {@link #setStrings},
+ * {@link DevicePolicyResources.Strings}) that was previously set by calling {@link #setStrings},
* meaning any subsequent calls to {@link #getString} for the provided IDs will
* return the default string from {@code defaultStringLoader}.
*
@@ -15179,14 +15256,14 @@
/**
* Returns the appropriate updated string for the {@code stringId} (see
- * {@link DevicePolicyResources.String}) if one was set using
+ * {@link DevicePolicyResources.Strings}) if one was set using
* {@link #setStrings}, otherwise returns the string from {@code defaultStringLoader}.
*
* <p>Also returns the string from {@code defaultStringLoader} if
- * {@link DevicePolicyResources.String#INVALID_ID} was passed.
+ * {@link DevicePolicyResources.Strings#UNDEFINED} was passed.
*
- * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a
- * {@link NullPointerException} is thrown.
+ * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+ * and the call to {@code defaultStringLoader} returned {@code null}.
*
* <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
* notified when a resource has been updated.
@@ -15201,7 +15278,7 @@
* @hide
*/
@SystemApi
- @NonNull
+ @Nullable
public String getString(
@NonNull @DevicePolicyResources.UpdatableStringId String stringId,
@NonNull Callable<String> defaultStringLoader) {
@@ -15236,8 +15313,8 @@
* {@link java.util.Formatter} and {@link java.lang.String#format}, (see
* {@link Resources#getString(int, Object...)}).
*
- * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a
- * {@link NullPointerException} is thrown.
+ * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+ * and the call to {@code defaultStringLoader} returned {@code null}.
*
* @param stringId The IDs to get the updated resource for.
* @param defaultStringLoader To get the default string if no updated string was set for
@@ -15247,7 +15324,7 @@
* @hide
*/
@SystemApi
- @NonNull
+ @Nullable
@SuppressLint("SamShouldBeLast")
public String getString(
@NonNull @DevicePolicyResources.UpdatableStringId String stringId,
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 2ad2010..ac39cb4 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -93,6 +93,173 @@
import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_PERSONAL_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_WORK_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.WORK_PROFILE_PAUSED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.LOCATION_AUTO_GRANTED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.WORK_PROFILE_DEFAULT_APPS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_WORK_ACCOUNT_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCOUNTS_SEARCH_KEYWORDS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_DEVICE_ADMIN_APP;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_DEVICE_ADMIN_APP_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_THIS_DEVICE_ADMIN_APP;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVE_DEVICE_ADMIN_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTIONS_APPS_COUNT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTIONS_APPS_COUNT_MINIMUM;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_CAMERA;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_LOCATION;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_MICROPHONE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_APPS_COUNT_ESTIMATED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_APPS_INSTALLED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_NONE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_CURRENT_INPUT_METHOD;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_DEFAULT_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_HTTP_PROXY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_INPUT_METHOD_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_LOCK_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_APPS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_BUG_REPORT_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_NETWORK_LOGS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_SECURITY_LOGS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_USAGE_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_WORK_DATA_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_WIPE_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_PERSONAL_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.APP_CAN_ACCESS_PERSONAL_DATA;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.APP_CAN_ACCESS_PERSONAL_PERMISSIONS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_PERSONAL_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PASSWORD_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PATTERN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PIN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_APPS_SEARCH_KEYWORDS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_WORK_AND_PERSONAL_APPS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECT_APPS_DIALOG_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECT_APPS_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTACT_YOUR_IT_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CROSS_PROFILE_CALENDAR_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CROSS_PROFILE_CALENDAR_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_ADMIN_POLICIES_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_ADMIN_SETTINGS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_MANAGED_WITHOUT_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_MANAGED_WITH_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DISABLED_BY_IT_ADMIN_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENTERPRISE_PRIVACY_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ERROR_MOVE_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_SETTINGS_FOR_WORK_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_UNLOCK_DISABLED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_FOR_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED_EXPLANATION;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FORGOT_PASSWORD_TEXT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FORGOT_PASSWORD_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.HOW_TO_DISCONNECT_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.IT_ADMIN_POLICY_DISABLING_INFO_URL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_BY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_PROFILE_SETTINGS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGE_DEVICE_ADMIN_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NEW_DEVICE_ADMIN_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NO_DEVICE_ADMINS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NUMBER_OF_DEVICE_ADMINS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NUMBER_OF_DEVICE_ADMINS_NONE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ONLY_CONNECT_TRUSTED_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.OTHER_OPTIONS_DISABLED_BY_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PASSWORD_RECENTLY_USED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_PROFILE_APP_SUBTEXT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PIN_RECENTLY_USED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REENTER_WORK_PROFILE_PASSWORD_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REENTER_WORK_PROFILE_PIN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_AND_UNINSTALL_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SELECT_DEVICE_ADMIN_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_POSTSETUP_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PASSWORD_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PATTERN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PIN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARING_REMOTE_BUGREPORT_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.UNINSTALL_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.USER_ADMIN_POLICIES_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_ADMIN_POLICIES_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_ALARM_RINGTONE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_APP_SUBTEXT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PATTERN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_REMOVE_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_REMOVE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONTACT_SEARCH_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONTACT_SEARCH_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_KEYBOARDS_AND_TOOLS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCATION_SWITCH_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCKED_NOTIFICATION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_ATTEMPTS_FAILED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_MANAGED_BY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOT_AVAILABLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_OFF_CONDITION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PASSWORD_REQUIRED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PRIVACY_POLICY_INFO;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_RINGTONE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SECURITY_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_OFF_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_ON_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_DETAIL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.YOUR_ACCESS_TO_THIS_DEVICE_TITLE;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT;
@@ -159,7 +326,8 @@
Drawables.WORK_PROFILE_OFF_ICON,
Drawables.WORK_PROFILE_USER_ICON
})
- public @interface UpdatableDrawableId {}
+ public @interface UpdatableDrawableId {
+ }
/**
* Identifiers to specify the desired style for the updatable device management system
@@ -174,7 +342,8 @@
Drawables.Style.OUTLINE,
Drawables.Style.DEFAULT
})
- public @interface UpdatableDrawableStyle {}
+ public @interface UpdatableDrawableStyle {
+ }
/**
* Identifiers to specify the location if the updatable device management system resource.
@@ -191,7 +360,8 @@
Drawables.Source.QUICK_SETTINGS,
Drawables.Source.STATUS_BAR
})
- public @interface UpdatableDrawableSource {}
+ public @interface UpdatableDrawableSource {
+ }
/**
* Resource identifiers used to update device management-related string resources.
@@ -231,7 +401,7 @@
PERSONAL_APP_SUSPENSION_TITLE, PERSONAL_APP_SUSPENSION_MESSAGE,
PERSONAL_APP_SUSPENSION_SOON_MESSAGE, PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE,
PRINTING_DISABLED_NAMED_ADMIN, LOCATION_CHANGED_TITLE, LOCATION_CHANGED_MESSAGE,
- NETWORK_LOGGING_TITLE, NETWORK_LOGGING_MESSAGE,
+ NETWORK_LOGGING_TITLE, NETWORK_LOGGING_MESSAGE,
NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION, NOTIFICATION_CHANNEL_DEVICE_ADMIN,
SWITCH_TO_WORK_LABEL, SWITCH_TO_PERSONAL_LABEL, FORWARD_INTENT_TO_WORK,
FORWARD_INTENT_TO_PERSONAL, RESOLVER_WORK_PROFILE_NOT_SUPPORTED, RESOLVER_PERSONAL_TAB,
@@ -257,7 +427,92 @@
SWITCH_TO_WORK_MESSAGE, SWITCH_TO_PERSONAL_MESSAGE, BLOCKED_BY_ADMIN_TITLE,
BLOCKED_FROM_PERSONAL_MESSAGE, BLOCKED_FROM_PERSONAL_MESSAGE,
BLOCKED_FROM_WORK_MESSAGE, Strings.MediaProvider.WORK_PROFILE_PAUSED_TITLE,
- WORK_PROFILE_PAUSED_MESSAGE
+ WORK_PROFILE_PAUSED_MESSAGE,
+
+ // Settings Strings
+ FACE_SETTINGS_FOR_WORK_TITLE, WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE,
+ WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK, WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE,
+ WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE,
+ WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE,
+ WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE,
+ WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE, WORK_PROFILE_LOCK_ATTEMPTS_FAILED,
+ ACCESSIBILITY_CATEGORY_WORK, ACCESSIBILITY_CATEGORY_PERSONAL,
+ ACCESSIBILITY_WORK_ACCOUNT_TITLE, ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE,
+ WORK_PROFILE_LOCATION_SWITCH_TITLE, SET_WORK_PROFILE_PASSWORD_HEADER,
+ SET_WORK_PROFILE_PIN_HEADER, SET_WORK_PROFILE_PATTERN_HEADER,
+ CONFIRM_WORK_PROFILE_PASSWORD_HEADER, CONFIRM_WORK_PROFILE_PIN_HEADER,
+ CONFIRM_WORK_PROFILE_PATTERN_HEADER, REENTER_WORK_PROFILE_PASSWORD_HEADER,
+ REENTER_WORK_PROFILE_PIN_HEADER, WORK_PROFILE_CONFIRM_PATTERN, WORK_PROFILE_CONFIRM_PIN,
+ WORK_PROFILE_PASSWORD_REQUIRED, WORK_PROFILE_SECURITY_TITLE,
+ WORK_PROFILE_UNIFY_LOCKS_TITLE, WORK_PROFILE_UNIFY_LOCKS_SUMMARY,
+ WORK_PROFILE_UNIFY_LOCKS_DETAIL, WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT,
+ WORK_PROFILE_KEYBOARDS_AND_TOOLS, WORK_PROFILE_NOT_AVAILABLE, WORK_PROFILE_SETTING,
+ WORK_PROFILE_SETTING_ON_SUMMARY, WORK_PROFILE_SETTING_OFF_SUMMARY, REMOVE_WORK_PROFILE,
+ DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING,
+ WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING, WORK_PROFILE_CONFIRM_REMOVE_TITLE,
+ WORK_PROFILE_CONFIRM_REMOVE_MESSAGE, WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS,
+ WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER, WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE,
+ WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY, WORK_PROFILE_RINGTONE_TITLE,
+ WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE, WORK_PROFILE_ALARM_RINGTONE_TITLE,
+ WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY,
+ ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE,
+ ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE,
+ WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER, WORK_PROFILE_LOCKED_NOTIFICATION_TITLE,
+ WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE,
+ WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY,
+ WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED, CONNECTED_WORK_AND_PERSONAL_APPS_TITLE,
+ CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA, ONLY_CONNECT_TRUSTED_APPS,
+ HOW_TO_DISCONNECT_APPS, CONNECT_APPS_DIALOG_TITLE, CONNECT_APPS_DIALOG_SUMMARY,
+ APP_CAN_ACCESS_PERSONAL_DATA, APP_CAN_ACCESS_PERSONAL_PERMISSIONS,
+ INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT,
+ INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT, WORK_PROFILE_MANAGED_BY, MANAGED_BY,
+ WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING, DISABLED_BY_IT_ADMIN_TITLE,
+ CONTACT_YOUR_IT_ADMIN, WORK_PROFILE_ADMIN_POLICIES_WARNING, USER_ADMIN_POLICIES_WARNING,
+ DEVICE_ADMIN_POLICIES_WARNING, WORK_PROFILE_OFF_CONDITION_TITLE,
+ MANAGED_PROFILE_SETTINGS_TITLE, WORK_PROFILE_CONTACT_SEARCH_TITLE,
+ WORK_PROFILE_CONTACT_SEARCH_SUMMARY, CROSS_PROFILE_CALENDAR_TITLE,
+ CROSS_PROFILE_CALENDAR_SUMMARY, ALWAYS_ON_VPN_PERSONAL_PROFILE, ALWAYS_ON_VPN_DEVICE,
+ ALWAYS_ON_VPN_WORK_PROFILE, CA_CERTS_PERSONAL_PROFILE, CA_CERTS_WORK_PROFILE,
+ CA_CERTS_DEVICE, ADMIN_CAN_LOCK_DEVICE, ADMIN_CAN_WIPE_DEVICE,
+ ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE,
+ ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE, DEVICE_MANAGED_WITHOUT_NAME,
+ DEVICE_MANAGED_WITH_NAME, WORK_PROFILE_APP_SUBTEXT, PERSONAL_PROFILE_APP_SUBTEXT,
+ FINGERPRINT_FOR_WORK, FACE_UNLOCK_DISABLED, FINGERPRINT_UNLOCK_DISABLED,
+ FINGERPRINT_UNLOCK_DISABLED_EXPLANATION, PIN_RECENTLY_USED, PASSWORD_RECENTLY_USED,
+ MANAGE_DEVICE_ADMIN_APPS, NUMBER_OF_DEVICE_ADMINS_NONE, NUMBER_OF_DEVICE_ADMINS,
+ FORGOT_PASSWORD_TITLE, FORGOT_PASSWORD_TEXT, ERROR_MOVE_DEVICE_ADMIN,
+ DEVICE_ADMIN_SETTINGS_TITLE, REMOVE_DEVICE_ADMIN, UNINSTALL_DEVICE_ADMIN,
+ REMOVE_AND_UNINSTALL_DEVICE_ADMIN, SELECT_DEVICE_ADMIN_APPS, NO_DEVICE_ADMINS,
+ ACTIVATE_DEVICE_ADMIN_APP, ACTIVATE_THIS_DEVICE_ADMIN_APP,
+ ACTIVATE_DEVICE_ADMIN_APP_TITLE, NEW_DEVICE_ADMIN_WARNING,
+ NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED, ACTIVE_DEVICE_ADMIN_WARNING,
+ SET_PROFILE_OWNER_TITLE, SET_PROFILE_OWNER_DIALOG_TITLE,
+ SET_PROFILE_OWNER_POSTSETUP_WARNING, OTHER_OPTIONS_DISABLED_BY_ADMIN,
+ REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION, IT_ADMIN_POLICY_DISABLING_INFO_URL,
+ SHARE_REMOTE_BUGREPORT_DIALOG_TITLE, SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT,
+ SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT, SHARING_REMOTE_BUGREPORT_MESSAGE,
+ MANAGED_DEVICE_INFO, MANAGED_DEVICE_INFO_SUMMARY, MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME,
+ ENTERPRISE_PRIVACY_HEADER, INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE,
+ CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE, YOUR_ACCESS_TO_THIS_DEVICE_TITLE,
+ ADMIN_CAN_SEE_WORK_DATA_WARNING, ADMIN_CAN_SEE_APPS_WARNING,
+ ADMIN_CAN_SEE_USAGE_WARNING, ADMIN_CAN_SEE_NETWORK_LOGS_WARNING,
+ ADMIN_CAN_SEE_BUG_REPORT_WARNING, ADMIN_CAN_SEE_SECURITY_LOGS_WARNING,
+ ADMIN_ACTION_NONE, ADMIN_ACTION_APPS_INSTALLED, ADMIN_ACTION_APPS_COUNT_ESTIMATED,
+ ADMIN_ACTIONS_APPS_COUNT_MINIMUM, ADMIN_ACTION_ACCESS_LOCATION,
+ ADMIN_ACTION_ACCESS_MICROPHONE, ADMIN_ACTION_ACCESS_CAMERA,
+ ADMIN_ACTION_SET_DEFAULT_APPS, ADMIN_ACTIONS_APPS_COUNT,
+ ADMIN_ACTION_SET_CURRENT_INPUT_METHOD, ADMIN_ACTION_SET_INPUT_METHOD_NAME,
+ ADMIN_ACTION_SET_HTTP_PROXY, WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY,
+ WORK_PROFILE_PRIVACY_POLICY_INFO, CONNECTED_APPS_SEARCH_KEYWORDS,
+ WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS, ACCOUNTS_SEARCH_KEYWORDS,
+ CONTROLLED_BY_ADMIN_SUMMARY, WORK_PROFILE_USER_LABEL, WORK_CATEGORY_HEADER,
+ PERSONAL_CATEGORY_HEADER,
+
+ // PermissionController Strings
+ WORK_PROFILE_DEFAULT_APPS_TITLE, HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE,
+ BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE, BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE,
+ BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE, FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE,
+ LOCATION_AUTO_GRANTED_MESSAGE
})
public @interface UpdatableStringId {
}
@@ -432,7 +687,8 @@
@SystemApi
public static final class Strings {
- private Strings() {}
+ private Strings() {
+ }
/**
* An ID for any string that can't be updated.
@@ -446,23 +702,1221 @@
private static Set<String> buildStringsSet() {
Set<String> strings = new HashSet<>();
+ strings.addAll(Settings.buildStringsSet());
strings.addAll(Launcher.buildStringsSet());
strings.addAll(SystemUi.buildStringsSet());
strings.addAll(Core.buildStringsSet());
strings.addAll(DocumentsUi.buildStringsSet());
strings.addAll(MediaProvider.buildStringsSet());
+ strings.addAll(PermissionController.buildStringsSet());
return strings;
}
/**
* Class containing the identifiers used to update device management-related system strings
+ * in the Settings package
+ *
+ * @hide
+ */
+ public static final class Settings {
+
+ private Settings() {
+ }
+
+ private static final String PREFIX = "Settings.";
+
+ /**
+ * Title shown for menu item that launches face settings or enrollment, for work profile
+ */
+ public static final String FACE_SETTINGS_FOR_WORK_TITLE =
+ PREFIX + "FACE_SETTINGS_FOR_WORK_TITLE";
+
+ /**
+ * Warning when removing the last fingerprint on a work profile
+ */
+ public static final String WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE =
+ PREFIX + "WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE";
+
+ /**
+ * Text letting the user know that their IT admin can't reset their screen lock if they
+ * forget it, and they can choose to set another lock that would be specifically for
+ * their work apps
+ */
+ public static final String WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK =
+ PREFIX + "WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK";
+
+ /**
+ * Message shown in screen lock picker for setting up a work profile screen lock
+ */
+ public static final String WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE =
+ PREFIX + "WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE";
+
+ /**
+ * Title for PreferenceScreen to launch picker for security method for the managed
+ * profile when there is none
+ */
+ public static final String WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE =
+ PREFIX + "WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE";
+
+ /**
+ * Content of the dialog shown when the user only has one attempt left to provide the
+ * work lock pattern before the work profile is removed
+ */
+ public static final String WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE =
+ PREFIX + "WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE";
+
+ /**
+ * Content of the dialog shown when the user only has one attempt left to provide the
+ * work lock pattern before the work profile is removed
+ */
+ public static final String WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE =
+ PREFIX + "WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE";
+
+ /**
+ * Content of the dialog shown when the user only has one attempt left to provide the
+ * work lock pattern before the work profile is removed
+ */
+ public static final String WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE =
+ PREFIX + "WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE";
+
+ /**
+ * Content of the dialog shown when the user has failed to provide the device lock too
+ * many times and the device is wiped
+ */
+ public static final String WORK_PROFILE_LOCK_ATTEMPTS_FAILED =
+ PREFIX + "WORK_PROFILE_LOCK_ATTEMPTS_FAILED";
+
+ /**
+ * Content description for work profile accounts group
+ */
+ public static final String ACCESSIBILITY_CATEGORY_WORK =
+ PREFIX + "ACCESSIBILITY_CATEGORY_WORK";
+
+ /**
+ * Content description for personal profile accounts group
+ */
+ public static final String ACCESSIBILITY_CATEGORY_PERSONAL =
+ PREFIX + "ACCESSIBILITY_CATEGORY_PERSONAL";
+
+ /**
+ * Content description for work profile details page title
+ */
+ public static final String ACCESSIBILITY_WORK_ACCOUNT_TITLE =
+ PREFIX + "ACCESSIBILITY_WORK_ACCOUNT_TITLE";
+
+ /**
+ * Content description for personal profile details page title
+ */
+ public static final String ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE =
+ PREFIX + "ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE";
+
+ /**
+ * Title for work profile location switch
+ */
+ public static final String WORK_PROFILE_LOCATION_SWITCH_TITLE =
+ PREFIX + "WORK_PROFILE_LOCATION_SWITCH_TITLE";
+
+ /**
+ * Header when setting work profile password
+ */
+ public static final String SET_WORK_PROFILE_PASSWORD_HEADER =
+ PREFIX + "SET_WORK_PROFILE_PASSWORD_HEADER";
+
+ /**
+ * Header when setting work profile PIN
+ */
+ public static final String SET_WORK_PROFILE_PIN_HEADER =
+ PREFIX + "SET_WORK_PROFILE_PIN_HEADER";
+
+ /**
+ * Header when setting work profile pattern
+ */
+ public static final String SET_WORK_PROFILE_PATTERN_HEADER =
+ PREFIX + "SET_WORK_PROFILE_PATTERN_HEADER";
+
+ /**
+ * Header when confirming work profile password
+ */
+ public static final String CONFIRM_WORK_PROFILE_PASSWORD_HEADER =
+ PREFIX + "CONFIRM_WORK_PROFILE_PASSWORD_HEADER";
+
+ /**
+ * Header when confirming work profile pin
+ */
+ public static final String CONFIRM_WORK_PROFILE_PIN_HEADER =
+ PREFIX + "CONFIRM_WORK_PROFILE_PIN_HEADER";
+
+ /**
+ * Header when confirming work profile pattern
+ */
+ public static final String CONFIRM_WORK_PROFILE_PATTERN_HEADER =
+ PREFIX + "CONFIRM_WORK_PROFILE_PATTERN_HEADER";
+
+ /**
+ * Header when re-entering work profile password
+ */
+ public static final String REENTER_WORK_PROFILE_PASSWORD_HEADER =
+ PREFIX + "REENTER_WORK_PROFILE_PASSWORD_HEADER";
+
+ /**
+ * Header when re-entering work profile pin
+ */
+ public static final String REENTER_WORK_PROFILE_PIN_HEADER =
+ PREFIX + "REENTER_WORK_PROFILE_PIN_HEADER";
+
+ /**
+ * Message to be used to explain the users that they need to enter their work pattern to
+ * continue a particular operation
+ */
+ public static final String WORK_PROFILE_CONFIRM_PATTERN =
+ PREFIX + "WORK_PROFILE_CONFIRM_PATTERN";
+
+ /**
+ * Message to be used to explain the users that they need to enter their work pin to
+ * continue a particular operation
+ */
+ public static final String WORK_PROFILE_CONFIRM_PIN =
+ PREFIX + "WORK_PROFILE_CONFIRM_PIN";
+
+ /**
+ * Message to be used to explain the users that they need to enter their work password
+ * to
+ * continue a particular operation
+ */
+ public static final String WORK_PROFILE_CONFIRM_PASSWORD =
+ PREFIX + "WORK_PROFILE_CONFIRM_PASSWORD";
+
+ /**
+ * This string shows = PREFIX + "shows"; up on a screen where a user can enter a pattern
+ * that lets them access
+ * their work profile. This is an extra security measure that's required for them to
+ * continue
+ */
+ public static final String WORK_PROFILE_PATTERN_REQUIRED =
+ PREFIX + "WORK_PROFILE_PATTERN_REQUIRED";
+
+ /**
+ * This string shows = PREFIX + "shows"; up on a screen where a user can enter a pin
+ * that lets them access
+ * their work profile. This is an extra security measure that's required for them to
+ * continue
+ */
+ public static final String WORK_PROFILE_PIN_REQUIRED =
+ PREFIX + "WORK_PROFILE_PIN_REQUIRED";
+
+ /**
+ * This string shows = PREFIX + "shows"; up on a screen where a user can enter a
+ * password that lets them access
+ * their work profile. This is an extra security measure that's required for them to
+ * continue
+ */
+ public static final String WORK_PROFILE_PASSWORD_REQUIRED =
+ PREFIX + "WORK_PROFILE_PASSWORD_REQUIRED";
+
+ /**
+ * Header for Work Profile security settings
+ */
+ public static final String WORK_PROFILE_SECURITY_TITLE =
+ PREFIX + "WORK_PROFILE_SECURITY_TITLE";
+
+ /**
+ * Header for Work Profile unify locks settings
+ */
+ public static final String WORK_PROFILE_UNIFY_LOCKS_TITLE =
+ PREFIX + "WORK_PROFILE_UNIFY_LOCKS_TITLE";
+
+ /**
+ * Setting option explanation to unify work and personal locks
+ */
+ public static final String WORK_PROFILE_UNIFY_LOCKS_SUMMARY =
+ PREFIX + "WORK_PROFILE_UNIFY_LOCKS_SUMMARY";
+
+ /**
+ * Further explanation when the user wants to unify work and personal locks
+ */
+ public static final String WORK_PROFILE_UNIFY_LOCKS_DETAIL =
+ PREFIX + "WORK_PROFILE_UNIFY_LOCKS_DETAIL";
+
+ /**
+ * Ask if the user wants to create a new lock for personal and work as the current work
+ * lock is not enough for the device
+ */
+ public static final String WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT =
+ PREFIX + "WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT";
+
+ /**
+ * Title of 'Work profile keyboards & tools' preference category
+ */
+ public static final String WORK_PROFILE_KEYBOARDS_AND_TOOLS =
+ PREFIX + "WORK_PROFILE_KEYBOARDS_AND_TOOLS";
+
+ /**
+ * Label for state when work profile is not available
+ */
+ public static final String WORK_PROFILE_NOT_AVAILABLE =
+ PREFIX + "WORK_PROFILE_NOT_AVAILABLE";
+
+ /**
+ * Label for work profile setting (to allow turning work profile on and off)
+ */
+ public static final String WORK_PROFILE_SETTING = PREFIX + "WORK_PROFILE_SETTING";
+
+ /**
+ * Description of the work profile setting when the work profile is on
+ */
+ public static final String WORK_PROFILE_SETTING_ON_SUMMARY =
+ PREFIX + "WORK_PROFILE_SETTING_ON_SUMMARY";
+
+ /**
+ * Description of the work profile setting when the work profile is off
+ */
+ public static final String WORK_PROFILE_SETTING_OFF_SUMMARY =
+ PREFIX + "WORK_PROFILE_SETTING_OFF_SUMMARY";
+
+ /**
+ * Button text to remove work profile
+ */
+ public static final String REMOVE_WORK_PROFILE = PREFIX + "REMOVE_WORK_PROFILE";
+
+ /**
+ * Text of message to show to device owner user whose administrator has installed a SSL
+ * CA Cert
+ */
+ public static final String DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING =
+ PREFIX + "DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING";
+
+ /**
+ * Text of message to show to work profile users whose administrator has installed a SSL
+ * CA Cert
+ */
+ public static final String WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING =
+ PREFIX + "WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING";
+
+ /**
+ * Work profile removal confirmation title
+ */
+ public static final String WORK_PROFILE_CONFIRM_REMOVE_TITLE =
+ PREFIX + "WORK_PROFILE_CONFIRM_REMOVE_TITLE";
+
+ /**
+ * Work profile removal confirmation message
+ */
+ public static final String WORK_PROFILE_CONFIRM_REMOVE_MESSAGE =
+ PREFIX + "WORK_PROFILE_CONFIRM_REMOVE_MESSAGE";
+
+ /**
+ * Toast shown when an app in the work profile attempts to open notification settings
+ * and apps in the work profile cannot access notification settings
+ */
+ public static final String WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS =
+ PREFIX + "WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS";
+
+ /**
+ * Work sound settings section header
+ */
+ public static final String WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER =
+ PREFIX + "WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER";
+
+ /**
+ * Title for the switch that enables syncing of personal ringtones to work profile
+ */
+ public static final String WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE =
+ PREFIX + "WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE";
+
+ /**
+ * Summary for the switch that enables syncing of personal ringtones to work profile
+ */
+ public static final String WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY =
+ PREFIX + "WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY";
+
+ /**
+ * Title for the option defining the work profile phone ringtone
+ */
+ public static final String WORK_PROFILE_RINGTONE_TITLE =
+ PREFIX + "WORK_PROFILE_RINGTONE_TITLE";
+
+ /**
+ * Title for the option defining the default work profile notification ringtone
+ */
+ public static final String WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE =
+ PREFIX + "WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE";
+
+ /**
+ * Title for the option defining the default work alarm ringtone
+ */
+ public static final String WORK_PROFILE_ALARM_RINGTONE_TITLE =
+ PREFIX + "WORK_PROFILE_ALARM_RINGTONE_TITLE";
+
+ /**
+ * Summary for sounds when sync with personal sounds is active
+ */
+ public static final String WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY =
+ PREFIX + "WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY";
+
+ /**
+ * Title for dialog shown when enabling sync with personal sounds
+ */
+ public static final String
+ ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE =
+ PREFIX + "ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE";
+
+ /**
+ * Message for dialog shown when using the same sounds for work events as for personal
+ * events
+ */
+ public static final String
+ ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE =
+ PREFIX + "ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE";
+
+ /**
+ * Work profile notifications section header
+ */
+ public static final String WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER =
+ PREFIX + "WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER";
+
+ /**
+ * Title for the option controlling notifications for work profile
+ */
+ public static final String WORK_PROFILE_LOCKED_NOTIFICATION_TITLE =
+ PREFIX + "WORK_PROFILE_LOCKED_NOTIFICATION_TITLE";
+
+ /**
+ * Title for redacting sensitive content on lockscreen for work profiles
+ */
+ public static final String WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE =
+ PREFIX + "WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE";
+
+ /**
+ * Summary for redacting sensitive content on lockscreen for work profiles
+ */
+ public static final String WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY =
+ PREFIX + "WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY";
+
+ /**
+ * Indicates that the work profile admin doesn't allow this notification listener to
+ * access
+ * work profile notifications
+ */
+ public static final String WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED =
+ PREFIX + "WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED";
+
+ /**
+ * This setting shows a user's connected work and personal apps.
+ */
+ public static final String CONNECTED_WORK_AND_PERSONAL_APPS_TITLE =
+ PREFIX + "CONNECTED_WORK_AND_PERSONAL_APPS_TITLE";
+
+ /**
+ * This text lets a user know that if they connect work and personal apps,
+ * they will share permissions and can access each other's data
+ */
+ public static final String CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA =
+ PREFIX + "CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA";
+
+ /**
+ * This text lets a user know that they should only connect work and personal apps if
+ * they
+ * trust the work app with their personal data
+ */
+ public static final String ONLY_CONNECT_TRUSTED_APPS =
+ PREFIX + "ONLY_CONNECT_TRUSTED_APPS";
+
+ /**
+ * This text lets a user know how to disconnect work and personal apps
+ */
+ public static final String HOW_TO_DISCONNECT_APPS = PREFIX + "HOW_TO_DISCONNECT_APPS";
+
+ /**
+ * Title of confirmation dialog when connecting work and personal apps
+ */
+ public static final String CONNECT_APPS_DIALOG_TITLE =
+ PREFIX + "CONNECT_APPS_DIALOG_TITLE";
+
+ /**
+ * This dialog is shown when a user tries to connect a work app to a personal
+ * app
+ */
+ public static final String CONNECT_APPS_DIALOG_SUMMARY =
+ PREFIX + "CONNECT_APPS_DIALOG_SUMMARY";
+
+ /**
+ * This text lets the user know that their work app will be able to access data in their
+ * personal app
+ */
+ public static final String APP_CAN_ACCESS_PERSONAL_DATA =
+ PREFIX + "APP_CAN_ACCESS_PERSONAL_DATA";
+
+ /**
+ * This text lets the user know that their work app will be able to use permissions in
+ * their personal app
+ */
+ public static final String APP_CAN_ACCESS_PERSONAL_PERMISSIONS =
+ PREFIX + "APP_CAN_ACCESS_PERSONAL_PERMISSIONS";
+
+ /**
+ * lets a user know that they need to install an app in their work profile in order to
+ * connect it to the corresponding personal app
+ */
+ public static final String INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT =
+ PREFIX + "INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT";
+
+ /**
+ * lets a user know that they need to install an app in their personal profile in order
+ * to
+ * connect it to the corresponding work app
+ */
+ public static final String INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT =
+ PREFIX + "INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT";
+
+ /**
+ * Header for showing the organisation managing the work profile
+ */
+ public static final String WORK_PROFILE_MANAGED_BY = PREFIX + "WORK_PROFILE_MANAGED_BY";
+
+ /**
+ * Summary showing the enterprise who manages the device or profile.
+ */
+ public static final String MANAGED_BY = PREFIX + "MANAGED_BY";
+
+ /**
+ * Warning message about disabling usage access on profile owner
+ */
+ public static final String WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING =
+ PREFIX + "WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING";
+
+ /**
+ * Title for dialog displayed when user taps a setting on their phone that's blocked by
+ * their IT admin
+ */
+ public static final String DISABLED_BY_IT_ADMIN_TITLE =
+ PREFIX + "DISABLED_BY_IT_ADMIN_TITLE";
+
+ /**
+ * Shown when the user tries to change phone settings that are blocked by their IT admin
+ */
+ public static final String CONTACT_YOUR_IT_ADMIN = PREFIX + "CONTACT_YOUR_IT_ADMIN";
+
+ /**
+ * warn user about policies the admin can set in a work profile
+ */
+ public static final String WORK_PROFILE_ADMIN_POLICIES_WARNING =
+ PREFIX + "WORK_PROFILE_ADMIN_POLICIES_WARNING";
+
+ /**
+ * warn user about policies the admin can set on a user
+ */
+ public static final String USER_ADMIN_POLICIES_WARNING =
+ PREFIX + "USER_ADMIN_POLICIES_WARNING";
+
+ /**
+ * warn user about policies the admin can set on a device
+ */
+ public static final String DEVICE_ADMIN_POLICIES_WARNING =
+ PREFIX + "DEVICE_ADMIN_POLICIES_WARNING";
+
+ /**
+ * Condition that work profile is off
+ */
+ public static final String WORK_PROFILE_OFF_CONDITION_TITLE =
+ PREFIX + "WORK_PROFILE_OFF_CONDITION_TITLE";
+
+ /**
+ * Title of work profile setting page
+ */
+ public static final String MANAGED_PROFILE_SETTINGS_TITLE =
+ PREFIX + "MANAGED_PROFILE_SETTINGS_TITLE";
+
+ /**
+ * Setting that lets a user's personal apps identify contacts using the user's work
+ * directory
+ */
+ public static final String WORK_PROFILE_CONTACT_SEARCH_TITLE =
+ PREFIX + "WORK_PROFILE_CONTACT_SEARCH_TITLE";
+
+ /**
+ * This setting lets a user's personal apps identify contacts using the user's work
+ * directory
+ */
+ public static final String WORK_PROFILE_CONTACT_SEARCH_SUMMARY =
+ PREFIX + "WORK_PROFILE_CONTACT_SEARCH_SUMMARY";
+
+ /**
+ * This setting lets the user show their work events on their personal calendar
+ */
+ public static final String CROSS_PROFILE_CALENDAR_TITLE =
+ PREFIX + "CROSS_PROFILE_CALENDAR_TITLE";
+
+ /**
+ * Setting description. If the user turns on this setting, they can see their work
+ * events on their personal calendar
+ */
+ public static final String CROSS_PROFILE_CALENDAR_SUMMARY =
+ PREFIX + "CROSS_PROFILE_CALENDAR_SUMMARY";
+
+ /**
+ * Label explaining that an always-on VPN was set by the admin in the personal profile
+ */
+ public static final String ALWAYS_ON_VPN_PERSONAL_PROFILE =
+ PREFIX + "ALWAYS_ON_VPN_PERSONAL_PROFILE";
+
+ /**
+ * Label explaining that an always-on VPN was set by the admin for the entire device
+ */
+ public static final String ALWAYS_ON_VPN_DEVICE = PREFIX + "ALWAYS_ON_VPN_DEVICE";
+
+ /**
+ * Label explaining that an always-on VPN was set by the admin in the work profile
+ */
+ public static final String ALWAYS_ON_VPN_WORK_PROFILE =
+ PREFIX + "ALWAYS_ON_VPN_WORK_PROFILE";
+
+ /**
+ * Label explaining that the admin installed trusted CA certificates in personal profile
+ */
+ public static final String CA_CERTS_PERSONAL_PROFILE =
+ PREFIX + "CA_CERTS_PERSONAL_PROFILE";
+
+ /**
+ * Label explaining that the admin installed trusted CA certificates in work profile
+ */
+ public static final String CA_CERTS_WORK_PROFILE = PREFIX + "CA_CERTS_WORK_PROFILE";
+
+ /**
+ * Label explaining that the admin installed trusted CA certificates for the entire
+ * device
+ */
+ public static final String CA_CERTS_DEVICE = PREFIX + "CA_CERTS_DEVICE";
+
+ /**
+ * Label explaining that the admin can lock the device and change the user's password
+ */
+ public static final String ADMIN_CAN_LOCK_DEVICE = PREFIX + "ADMIN_CAN_LOCK_DEVICE";
+
+ /**
+ * Label explaining that the admin can wipe the device remotely
+ */
+ public static final String ADMIN_CAN_WIPE_DEVICE = PREFIX + "ADMIN_CAN_WIPE_DEVICE";
+
+ /**
+ * Label explaining that the admin configured the device to wipe itself when the
+ * password is mistyped too many times
+ */
+ public static final String ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE =
+ PREFIX + "ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE";
+
+ /**
+ * Label explaining that the admin configured the work profile to wipe itself when the
+ * password is mistyped too many times
+ */
+ public static final String ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE =
+ PREFIX + "ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE";
+
+ /**
+ * Message indicating that the device is enterprise-managed by a Device Owner
+ */
+ public static final String DEVICE_MANAGED_WITHOUT_NAME =
+ PREFIX + "DEVICE_MANAGED_WITHOUT_NAME";
+
+ /**
+ * Message indicating that the device is enterprise-managed by a Device Owner
+ */
+ public static final String DEVICE_MANAGED_WITH_NAME =
+ PREFIX + "DEVICE_MANAGED_WITH_NAME";
+
+ /**
+ * Subtext of work profile app for current setting
+ */
+ public static final String WORK_PROFILE_APP_SUBTEXT =
+ PREFIX + "WORK_PROFILE_APP_SUBTEXT";
+
+ /**
+ * Subtext of personal profile app for current setting
+ */
+ public static final String PERSONAL_PROFILE_APP_SUBTEXT =
+ PREFIX + "PERSONAL_PROFILE_APP_SUBTEXT";
+
+ /**
+ * Title shown for work menu item that launches fingerprint settings or enrollment
+ */
+ public static final String FINGERPRINT_FOR_WORK = PREFIX + "FINGERPRINT_FOR_WORK";
+
+ /**
+ * Message shown in face enrollment dialog, when face unlock is disabled by device admin
+ */
+ public static final String FACE_UNLOCK_DISABLED = PREFIX + "FACE_UNLOCK_DISABLED";
+
+ /**
+ * message shown in fingerprint enrollment dialog, when fingerprint unlock is disabled
+ * by device admin
+ */
+ public static final String FINGERPRINT_UNLOCK_DISABLED =
+ PREFIX + "FINGERPRINT_UNLOCK_DISABLED";
+
+ /**
+ * Text shown in fingerprint settings explaining what the fingerprint can be used for in
+ * the case unlocking is disabled
+ */
+ public static final String FINGERPRINT_UNLOCK_DISABLED_EXPLANATION =
+ PREFIX + "FINGERPRINT_UNLOCK_DISABLED_EXPLANATION";
+
+ /**
+ * Error shown when in PIN mode and PIN has been used recently
+ */
+ public static final String PIN_RECENTLY_USED = PREFIX + "PIN_RECENTLY_USED";
+
+ /**
+ * Error shown when in PASSWORD mode and password has been used recently
+ */
+ public static final String PASSWORD_RECENTLY_USED = PREFIX + "PASSWORD_RECENTLY_USED";
+
+ /**
+ * Title of preference to manage device admin apps
+ */
+ public static final String MANAGE_DEVICE_ADMIN_APPS =
+ PREFIX + "MANAGE_DEVICE_ADMIN_APPS";
+
+ /**
+ * Inform the user that currently no device admin apps are installed and active
+ */
+ public static final String NUMBER_OF_DEVICE_ADMINS_NONE =
+ PREFIX + "NUMBER_OF_DEVICE_ADMINS_NONE";
+
+ /**
+ * Inform the user how many device admin apps are installed and active
+ */
+ public static final String NUMBER_OF_DEVICE_ADMINS = PREFIX + "NUMBER_OF_DEVICE_ADMINS";
+
+ /**
+ * Title that asks the user to contact the IT admin to reset password
+ */
+ public static final String FORGOT_PASSWORD_TITLE = PREFIX + "FORGOT_PASSWORD_TITLE";
+
+ /**
+ * Content that asks the user to contact the IT admin to reset password
+ */
+ public static final String FORGOT_PASSWORD_TEXT = PREFIX + "FORGOT_PASSWORD_TEXT";
+
+ /**
+ * Error message shown when trying to move device administrators to external disks, such
+ * as SD card
+ */
+ public static final String ERROR_MOVE_DEVICE_ADMIN = PREFIX + "ERROR_MOVE_DEVICE_ADMIN";
+
+ /**
+ * Device admin app settings title
+ */
+ public static final String DEVICE_ADMIN_SETTINGS_TITLE =
+ PREFIX + "DEVICE_ADMIN_SETTINGS_TITLE";
+
+ /**
+ * Button to remove the active device admin app
+ */
+ public static final String REMOVE_DEVICE_ADMIN = PREFIX + "REMOVE_DEVICE_ADMIN";
+
+ /**
+ * Button to uninstall the device admin app
+ */
+ public static final String UNINSTALL_DEVICE_ADMIN = PREFIX + "UNINSTALL_DEVICE_ADMIN";
+
+ /**
+ * Button to deactivate and uninstall the device admin app
+ */
+ public static final String REMOVE_AND_UNINSTALL_DEVICE_ADMIN =
+ PREFIX + "REMOVE_AND_UNINSTALL_DEVICE_ADMIN";
+
+ /**
+ * Title for selecting device admin apps
+ */
+ public static final String SELECT_DEVICE_ADMIN_APPS =
+ PREFIX + "SELECT_DEVICE_ADMIN_APPS";
+
+ /**
+ * Message when there are no available device admin apps to display
+ */
+ public static final String NO_DEVICE_ADMINS = PREFIX + "NO_DEVICE_ADMINS";
+
+ /**
+ * Title for screen to add a device admin app
+ */
+ public static final String ACTIVATE_DEVICE_ADMIN_APP =
+ PREFIX + "ACTIVATE_DEVICE_ADMIN_APP";
+
+ /**
+ * Label for button to set the active device admin
+ */
+ public static final String ACTIVATE_THIS_DEVICE_ADMIN_APP =
+ PREFIX + "ACTIVATE_THIS_DEVICE_ADMIN_APP";
+
+ /**
+ * Activate a specific device admin app title
+ */
+ public static final String ACTIVATE_DEVICE_ADMIN_APP_TITLE =
+ PREFIX + "ACTIVATE_DEVICE_ADMIN_APP_TITLE";
+
+ /**
+ * Device admin warning message about policies a not active admin can use
+ */
+ public static final String NEW_DEVICE_ADMIN_WARNING =
+ PREFIX + "NEW_DEVICE_ADMIN_WARNING";
+
+ /**
+ * Simplified device admin warning message
+ */
+ public static final String NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED =
+ PREFIX + "NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED";
+
+ /**
+ * Device admin warning message about policies the active admin can use
+ */
+ public static final String ACTIVE_DEVICE_ADMIN_WARNING =
+ PREFIX + "ACTIVE_DEVICE_ADMIN_WARNING";
+
+ /**
+ * Title for screen to set a profile owner
+ */
+ public static final String SET_PROFILE_OWNER_TITLE = PREFIX + "SET_PROFILE_OWNER_TITLE";
+
+ /**
+ * Simplified title for dialog to set a profile owner
+ */
+ public static final String SET_PROFILE_OWNER_DIALOG_TITLE =
+ PREFIX + "SET_PROFILE_OWNER_DIALOG_TITLE";
+
+ /**
+ * Warning when trying to add a profile owner admin after setup has completed
+ */
+ public static final String SET_PROFILE_OWNER_POSTSETUP_WARNING =
+ PREFIX + "SET_PROFILE_OWNER_POSTSETUP_WARNING";
+
+ /**
+ * Message displayed to let the user know that some of the options are disabled by admin
+ */
+ public static final String OTHER_OPTIONS_DISABLED_BY_ADMIN =
+ PREFIX + "OTHER_OPTIONS_DISABLED_BY_ADMIN";
+
+ /**
+ * This is shown if the authenticator for a given account fails to remove it due to
+ * admin restrictions
+ */
+ public static final String REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION =
+ PREFIX + "REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION";
+
+ /**
+ * Url for learning more about IT admin policy disabling
+ */
+ public static final String IT_ADMIN_POLICY_DISABLING_INFO_URL =
+ PREFIX + "IT_ADMIN_POLICY_DISABLING_INFO_URL";
+
+ /**
+ * Title of dialog shown to ask for user consent for sharing a bugreport that was
+ * requested
+ * remotely by the IT administrator
+ */
+ public static final String SHARE_REMOTE_BUGREPORT_DIALOG_TITLE =
+ PREFIX + "SHARE_REMOTE_BUGREPORT_DIALOG_TITLE";
+
+ /**
+ * Message of a dialog shown to ask for user consent for sharing a bugreport that was
+ * requested remotely by the IT administrator
+ */
+ public static final String SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT =
+ PREFIX + "SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT";
+
+ /**
+ * Message of a dialog shown to ask for user consent for sharing a bugreport that was
+ * requested remotely by the IT administrator and it's still being taken
+ */
+ public static final String SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT =
+ PREFIX + "SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT";
+
+ /**
+ * Message of a dialog shown to inform that the remote bugreport that was requested
+ * remotely by the IT administrator is still being taken and will be shared when
+ * finished
+ */
+ public static final String SHARING_REMOTE_BUGREPORT_MESSAGE =
+ PREFIX + "SHARING_REMOTE_BUGREPORT_MESSAGE";
+
+ /**
+ * Managed device information screen title
+ */
+ public static final String MANAGED_DEVICE_INFO = PREFIX + "MANAGED_DEVICE_INFO";
+
+ /**
+ * Summary for managed device info section
+ */
+ public static final String MANAGED_DEVICE_INFO_SUMMARY =
+ PREFIX + "MANAGED_DEVICE_INFO_SUMMARY";
+
+ /**
+ * Summary for managed device info section including organization name
+ */
+ public static final String MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME =
+ PREFIX + "MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME";
+
+ /**
+ * Enterprise Privacy settings header, summarizing the powers that the admin has
+ */
+ public static final String ENTERPRISE_PRIVACY_HEADER =
+ PREFIX + "ENTERPRISE_PRIVACY_HEADER";
+
+ /**
+ * Types of information your organization can see section title
+ */
+ public static final String INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE =
+ PREFIX + "INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE";
+
+ /**
+ * Changes made by your organization's admin section title
+ */
+ public static final String CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE =
+ PREFIX + "CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE";
+
+ /**
+ * Your access to this device section title
+ */
+ public static final String YOUR_ACCESS_TO_THIS_DEVICE_TITLE =
+ PREFIX + "YOUR_ACCESS_TO_THIS_DEVICE_TITLE";
+
+ /**
+ * Things the admin can see: data associated with the work account
+ */
+ public static final String ADMIN_CAN_SEE_WORK_DATA_WARNING =
+ PREFIX + "ADMIN_CAN_SEE_WORK_DATA_WARNING";
+
+ /**
+ * Things the admin can see: Apps installed on the device
+ */
+ public static final String ADMIN_CAN_SEE_APPS_WARNING =
+ PREFIX + "ADMIN_CAN_SEE_APPS_WARNING";
+
+ /**
+ * Things the admin can see: Amount of time and data spent in each app
+ */
+ public static final String ADMIN_CAN_SEE_USAGE_WARNING =
+ PREFIX + "ADMIN_CAN_SEE_USAGE_WARNING";
+
+ /**
+ * Things the admin can see: Most recent network traffic log
+ */
+ public static final String ADMIN_CAN_SEE_NETWORK_LOGS_WARNING =
+ PREFIX + "ADMIN_CAN_SEE_NETWORK_LOGS_WARNING";
+ /**
+ * Things the admin can see: Most recent bug report
+ */
+ public static final String ADMIN_CAN_SEE_BUG_REPORT_WARNING =
+ PREFIX + "ADMIN_CAN_SEE_BUG_REPORT_WARNING";
+
+ /**
+ * Things the admin can see: Security logs
+ */
+ public static final String ADMIN_CAN_SEE_SECURITY_LOGS_WARNING =
+ PREFIX + "ADMIN_CAN_SEE_SECURITY_LOGS_WARNING";
+
+ /**
+ * Indicate that the admin never took a given action so far (e.g. did not retrieve
+ * security logs or request bug reports).
+ */
+ public static final String ADMIN_ACTION_NONE = PREFIX + "ADMIN_ACTION_NONE";
+
+ /**
+ * Indicate that the admin installed one or more apps on the device
+ */
+ public static final String ADMIN_ACTION_APPS_INSTALLED =
+ PREFIX + "ADMIN_ACTION_APPS_INSTALLED";
+
+ /**
+ * Explaining that the number of apps is an estimation
+ */
+ public static final String ADMIN_ACTION_APPS_COUNT_ESTIMATED =
+ PREFIX + "ADMIN_ACTION_APPS_COUNT_ESTIMATED";
+
+ /**
+ * Indicating the minimum number of apps that a label refers to
+ */
+ public static final String ADMIN_ACTIONS_APPS_COUNT_MINIMUM =
+ PREFIX + "ADMIN_ACTIONS_APPS_COUNT_MINIMUM";
+
+ /**
+ * Indicate that the admin granted one or more apps access to the device's location
+ */
+ public static final String ADMIN_ACTION_ACCESS_LOCATION =
+ PREFIX + "ADMIN_ACTION_ACCESS_LOCATION";
+
+ /**
+ * Indicate that the admin granted one or more apps access to the microphone
+ */
+ public static final String ADMIN_ACTION_ACCESS_MICROPHONE =
+ PREFIX + "ADMIN_ACTION_ACCESS_MICROPHONE";
+
+ /**
+ * Indicate that the admin granted one or more apps access to the camera
+ */
+ public static final String ADMIN_ACTION_ACCESS_CAMERA =
+ PREFIX + "ADMIN_ACTION_ACCESS_CAMERA";
+
+ /**
+ * Indicate that the admin set one or more apps as defaults for common actions
+ */
+ public static final String ADMIN_ACTION_SET_DEFAULT_APPS =
+ PREFIX + "ADMIN_ACTION_SET_DEFAULT_APPS";
+
+ /**
+ * Indicate the number of apps that a label refers to
+ */
+ public static final String ADMIN_ACTIONS_APPS_COUNT =
+ PREFIX + "ADMIN_ACTIONS_APPS_COUNT";
+
+ /**
+ * Indicate that the current input method was set by the admin
+ */
+ public static final String ADMIN_ACTION_SET_CURRENT_INPUT_METHOD =
+ PREFIX + "ADMIN_ACTION_SET_CURRENT_INPUT_METHOD";
+
+ /**
+ * The input method set by the admin
+ */
+ public static final String ADMIN_ACTION_SET_INPUT_METHOD_NAME =
+ PREFIX + "ADMIN_ACTION_SET_INPUT_METHOD_NAME";
+
+ /**
+ * Indicate that a global HTTP proxy was set by the admin
+ */
+ public static final String ADMIN_ACTION_SET_HTTP_PROXY =
+ PREFIX + "ADMIN_ACTION_SET_HTTP_PROXY";
+
+ /**
+ * Summary for Enterprise Privacy settings, explaining what the user can expect to find
+ * under it
+ */
+ public static final String WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY =
+ PREFIX + "WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY";
+
+ /**
+ * Setting on privacy settings screen that will show work policy info
+ */
+ public static final String WORK_PROFILE_PRIVACY_POLICY_INFO =
+ PREFIX + "WORK_PROFILE_PRIVACY_POLICY_INFO";
+
+ /**
+ * Search keywords for connected work and personal apps
+ */
+ public static final String CONNECTED_APPS_SEARCH_KEYWORDS =
+ PREFIX + "CONNECTED_APPS_SEARCH_KEYWORDS";
+
+ /**
+ * Work profile unification keywords
+ */
+ public static final String WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS =
+ PREFIX + "WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS";
+
+ /**
+ * Accounts keywords
+ */
+ public static final String ACCOUNTS_SEARCH_KEYWORDS =
+ PREFIX + "ACCOUNTS_SEARCH_KEYWORDS";
+
+ /**
+ * Summary for settings preference disabled by administrator
+ */
+ public static final String CONTROLLED_BY_ADMIN_SUMMARY =
+ PREFIX + "CONTROLLED_BY_ADMIN_SUMMARY";
+
+ /**
+ * User label for a work profile
+ */
+ public static final String WORK_PROFILE_USER_LABEL = PREFIX + "WORK_PROFILE_USER_LABEL";
+
+ /**
+ * Header for items under the work user
+ */
+ public static final String WORK_CATEGORY_HEADER = PREFIX + "WORK_CATEGORY_HEADER";
+
+ /**
+ * Header for items under the personal user
+ */
+ public static final String PERSONAL_CATEGORY_HEADER = PREFIX + "category_personal";
+
+ /**
+ * @hide
+ */
+ static Set<String> buildStringsSet() {
+ Set<String> strings = new HashSet<>();
+ strings.add(FACE_SETTINGS_FOR_WORK_TITLE);
+ strings.add(WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE);
+ strings.add(WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK);
+ strings.add(WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE);
+ strings.add(WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE);
+ strings.add(WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE);
+ strings.add(WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE);
+ strings.add(WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE);
+ strings.add(WORK_PROFILE_LOCK_ATTEMPTS_FAILED);
+ strings.add(ACCESSIBILITY_CATEGORY_WORK);
+ strings.add(ACCESSIBILITY_CATEGORY_PERSONAL);
+ strings.add(ACCESSIBILITY_WORK_ACCOUNT_TITLE);
+ strings.add(ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE);
+ strings.add(WORK_PROFILE_LOCATION_SWITCH_TITLE);
+ strings.add(SET_WORK_PROFILE_PASSWORD_HEADER);
+ strings.add(SET_WORK_PROFILE_PIN_HEADER);
+ strings.add(SET_WORK_PROFILE_PATTERN_HEADER);
+ strings.add(CONFIRM_WORK_PROFILE_PASSWORD_HEADER);
+ strings.add(CONFIRM_WORK_PROFILE_PIN_HEADER);
+ strings.add(CONFIRM_WORK_PROFILE_PATTERN_HEADER);
+ strings.add(REENTER_WORK_PROFILE_PASSWORD_HEADER);
+ strings.add(REENTER_WORK_PROFILE_PIN_HEADER);
+ strings.add(WORK_PROFILE_CONFIRM_PATTERN);
+ strings.add(WORK_PROFILE_CONFIRM_PIN);
+ strings.add(WORK_PROFILE_PASSWORD_REQUIRED);
+ strings.add(WORK_PROFILE_SECURITY_TITLE);
+ strings.add(WORK_PROFILE_UNIFY_LOCKS_TITLE);
+ strings.add(WORK_PROFILE_UNIFY_LOCKS_SUMMARY);
+ strings.add(WORK_PROFILE_UNIFY_LOCKS_DETAIL);
+ strings.add(WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT);
+ strings.add(WORK_PROFILE_KEYBOARDS_AND_TOOLS);
+ strings.add(WORK_PROFILE_NOT_AVAILABLE);
+ strings.add(WORK_PROFILE_SETTING);
+ strings.add(WORK_PROFILE_SETTING_ON_SUMMARY);
+ strings.add(WORK_PROFILE_SETTING_OFF_SUMMARY);
+ strings.add(REMOVE_WORK_PROFILE);
+ strings.add(DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING);
+ strings.add(WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING);
+ strings.add(WORK_PROFILE_CONFIRM_REMOVE_TITLE);
+ strings.add(WORK_PROFILE_CONFIRM_REMOVE_MESSAGE);
+ strings.add(WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS);
+ strings.add(WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER);
+ strings.add(WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE);
+ strings.add(WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY);
+ strings.add(WORK_PROFILE_RINGTONE_TITLE);
+ strings.add(WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE);
+ strings.add(WORK_PROFILE_ALARM_RINGTONE_TITLE);
+ strings.add(WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY);
+ strings.add(ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE);
+ strings.add(ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE);
+ strings.add(WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER);
+ strings.add(WORK_PROFILE_LOCKED_NOTIFICATION_TITLE);
+ strings.add(WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE);
+ strings.add(WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY);
+ strings.add(WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED);
+ strings.add(CONNECTED_WORK_AND_PERSONAL_APPS_TITLE);
+ strings.add(CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA);
+ strings.add(ONLY_CONNECT_TRUSTED_APPS);
+ strings.add(HOW_TO_DISCONNECT_APPS);
+ strings.add(CONNECT_APPS_DIALOG_TITLE);
+ strings.add(CONNECT_APPS_DIALOG_SUMMARY);
+ strings.add(APP_CAN_ACCESS_PERSONAL_DATA);
+ strings.add(APP_CAN_ACCESS_PERSONAL_PERMISSIONS);
+ strings.add(INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT);
+ strings.add(INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT);
+ strings.add(WORK_PROFILE_MANAGED_BY);
+ strings.add(MANAGED_BY);
+ strings.add(WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING);
+ strings.add(DISABLED_BY_IT_ADMIN_TITLE);
+ strings.add(CONTACT_YOUR_IT_ADMIN);
+ strings.add(WORK_PROFILE_ADMIN_POLICIES_WARNING);
+ strings.add(USER_ADMIN_POLICIES_WARNING);
+ strings.add(DEVICE_ADMIN_POLICIES_WARNING);
+ strings.add(WORK_PROFILE_OFF_CONDITION_TITLE);
+ strings.add(MANAGED_PROFILE_SETTINGS_TITLE);
+ strings.add(WORK_PROFILE_CONTACT_SEARCH_TITLE);
+ strings.add(WORK_PROFILE_CONTACT_SEARCH_SUMMARY);
+ strings.add(CROSS_PROFILE_CALENDAR_TITLE);
+ strings.add(CROSS_PROFILE_CALENDAR_SUMMARY);
+ strings.add(ALWAYS_ON_VPN_PERSONAL_PROFILE);
+ strings.add(ALWAYS_ON_VPN_DEVICE);
+ strings.add(ALWAYS_ON_VPN_WORK_PROFILE);
+ strings.add(CA_CERTS_PERSONAL_PROFILE);
+ strings.add(CA_CERTS_WORK_PROFILE);
+ strings.add(CA_CERTS_DEVICE);
+ strings.add(ADMIN_CAN_LOCK_DEVICE);
+ strings.add(ADMIN_CAN_WIPE_DEVICE);
+ strings.add(ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE);
+ strings.add(ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE);
+ strings.add(DEVICE_MANAGED_WITHOUT_NAME);
+ strings.add(DEVICE_MANAGED_WITH_NAME);
+ strings.add(WORK_PROFILE_APP_SUBTEXT);
+ strings.add(PERSONAL_PROFILE_APP_SUBTEXT);
+ strings.add(FINGERPRINT_FOR_WORK);
+ strings.add(FACE_UNLOCK_DISABLED);
+ strings.add(FINGERPRINT_UNLOCK_DISABLED);
+ strings.add(FINGERPRINT_UNLOCK_DISABLED_EXPLANATION);
+ strings.add(PIN_RECENTLY_USED);
+ strings.add(PASSWORD_RECENTLY_USED);
+ strings.add(MANAGE_DEVICE_ADMIN_APPS);
+ strings.add(NUMBER_OF_DEVICE_ADMINS_NONE);
+ strings.add(NUMBER_OF_DEVICE_ADMINS);
+ strings.add(FORGOT_PASSWORD_TITLE);
+ strings.add(FORGOT_PASSWORD_TEXT);
+ strings.add(ERROR_MOVE_DEVICE_ADMIN);
+ strings.add(DEVICE_ADMIN_SETTINGS_TITLE);
+ strings.add(REMOVE_DEVICE_ADMIN);
+ strings.add(UNINSTALL_DEVICE_ADMIN);
+ strings.add(REMOVE_AND_UNINSTALL_DEVICE_ADMIN);
+ strings.add(SELECT_DEVICE_ADMIN_APPS);
+ strings.add(NO_DEVICE_ADMINS);
+ strings.add(ACTIVATE_DEVICE_ADMIN_APP);
+ strings.add(ACTIVATE_THIS_DEVICE_ADMIN_APP);
+ strings.add(ACTIVATE_DEVICE_ADMIN_APP_TITLE);
+ strings.add(NEW_DEVICE_ADMIN_WARNING);
+ strings.add(NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED);
+ strings.add(ACTIVE_DEVICE_ADMIN_WARNING);
+ strings.add(SET_PROFILE_OWNER_TITLE);
+ strings.add(SET_PROFILE_OWNER_DIALOG_TITLE);
+ strings.add(SET_PROFILE_OWNER_POSTSETUP_WARNING);
+ strings.add(OTHER_OPTIONS_DISABLED_BY_ADMIN);
+ strings.add(REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION);
+ strings.add(IT_ADMIN_POLICY_DISABLING_INFO_URL);
+ strings.add(SHARE_REMOTE_BUGREPORT_DIALOG_TITLE);
+ strings.add(SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT);
+ strings.add(SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT);
+ strings.add(SHARING_REMOTE_BUGREPORT_MESSAGE);
+ strings.add(MANAGED_DEVICE_INFO);
+ strings.add(MANAGED_DEVICE_INFO_SUMMARY);
+ strings.add(MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME);
+ strings.add(ENTERPRISE_PRIVACY_HEADER);
+ strings.add(INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE);
+ strings.add(CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE);
+ strings.add(YOUR_ACCESS_TO_THIS_DEVICE_TITLE);
+ strings.add(ADMIN_CAN_SEE_WORK_DATA_WARNING);
+ strings.add(ADMIN_CAN_SEE_APPS_WARNING);
+ strings.add(ADMIN_CAN_SEE_USAGE_WARNING);
+ strings.add(ADMIN_CAN_SEE_NETWORK_LOGS_WARNING);
+ strings.add(ADMIN_CAN_SEE_BUG_REPORT_WARNING);
+ strings.add(ADMIN_CAN_SEE_SECURITY_LOGS_WARNING);
+ strings.add(ADMIN_ACTION_NONE);
+ strings.add(ADMIN_ACTION_APPS_INSTALLED);
+ strings.add(ADMIN_ACTION_APPS_COUNT_ESTIMATED);
+ strings.add(ADMIN_ACTIONS_APPS_COUNT_MINIMUM);
+ strings.add(ADMIN_ACTION_ACCESS_LOCATION);
+ strings.add(ADMIN_ACTION_ACCESS_MICROPHONE);
+ strings.add(ADMIN_ACTION_ACCESS_CAMERA);
+ strings.add(ADMIN_ACTION_SET_DEFAULT_APPS);
+ strings.add(ADMIN_ACTIONS_APPS_COUNT);
+ strings.add(ADMIN_ACTION_SET_CURRENT_INPUT_METHOD);
+ strings.add(ADMIN_ACTION_SET_INPUT_METHOD_NAME);
+ strings.add(ADMIN_ACTION_SET_HTTP_PROXY);
+ strings.add(WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY);
+ strings.add(WORK_PROFILE_PRIVACY_POLICY_INFO);
+ strings.add(CONNECTED_APPS_SEARCH_KEYWORDS);
+ strings.add(WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS);
+ strings.add(ACCOUNTS_SEARCH_KEYWORDS);
+ strings.add(CONTROLLED_BY_ADMIN_SUMMARY);
+ strings.add(WORK_PROFILE_USER_LABEL);
+ strings.add(WORK_CATEGORY_HEADER);
+ strings.add(PERSONAL_CATEGORY_HEADER);
+ return strings;
+ }
+ }
+
+ /**
+ * Class containing the identifiers used to update device management-related system strings
* in the Launcher package.
*
* @hide
*/
public static final class Launcher {
- private Launcher(){}
+ private Launcher() {
+ }
private static final String PREFIX = "Launcher.";
@@ -576,6 +2030,7 @@
private SystemUi() {
}
+
private static final String PREFIX = "SystemUi.";
/**
@@ -649,9 +2104,9 @@
PREFIX + "QS_MSG_NAMED_WORK_PROFILE_MONITORING";
/**
- * Disclosure at the bottom of Quick Settings to indicate network activity is visible to
+ * Disclosure at the bottom of Quick Settings to indicate network activity is visible to
* admin.
- */
+ */
public static final String QS_MSG_WORK_PROFILE_NETWORK =
PREFIX + "QS_MSG_WORK_PROFILE_NETWORK";
@@ -1413,5 +2868,71 @@
return strings;
}
}
+
+ /**
+ * Class containing the identifiers used to update device management-related system strings
+ * in the PermissionController module.
+ */
+ public static final class PermissionController {
+
+ private PermissionController() {
+ }
+
+ private static final String PREFIX = "PermissionController.";
+
+ /**
+ * Title for settings page to show default apps for work.
+ */
+ public static final String WORK_PROFILE_DEFAULT_APPS_TITLE =
+ PREFIX + "WORK_PROFILE_DEFAULT_APPS_TITLE";
+
+ /**
+ * Summary indicating that a home role holder app is missing work profile support.
+ */
+ public static final String HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE =
+ PREFIX + "HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE";
+
+ /**
+ * Summary of a permission switch in Settings when the background access is denied by an
+ * admin.
+ */
+ public static final String BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE =
+ PREFIX + "BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE";
+
+ /**
+ * Summary of a permission switch in Settings when the background access is enabled by
+ * an admin.
+ */
+ public static final String BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE =
+ PREFIX + "BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE";
+
+ /**
+ * Summary of a permission switch in Settings when the foreground access is enabled by
+ * an admin.
+ */
+ public static final String FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE =
+ PREFIX + "FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE";
+
+ /**
+ * Body of the notification shown to notify the user that the location permission has
+ * been granted to an app, accepts app name as a param.
+ */
+ public static final String LOCATION_AUTO_GRANTED_MESSAGE =
+ PREFIX + "LOCATION_AUTO_GRANTED_MESSAGE";
+
+ /**
+ * @hide
+ */
+ static Set<String> buildStringsSet() {
+ Set<String> strings = new HashSet<>();
+ strings.add(WORK_PROFILE_DEFAULT_APPS_TITLE);
+ strings.add(HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE);
+ strings.add(BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE);
+ strings.add(BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE);
+ strings.add(FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE);
+ strings.add(LOCATION_AUTO_GRANTED_MESSAGE);
+ return strings;
+ }
+ }
}
}
diff --git a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
index 8c232c0..1f7ae4a 100644
--- a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
+++ b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
@@ -25,6 +25,7 @@
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import android.stats.devicepolicy.DevicePolicyEnums;
import java.util.Locale;
@@ -52,6 +53,7 @@
@SuppressLint("UseIcu")
@Nullable private final Locale mLocale;
private final boolean mDeviceOwnerCanGrantSensorsPermissions;
+ @NonNull private final PersistableBundle mAdminExtras;
private FullyManagedDeviceProvisioningParams(
@NonNull ComponentName deviceAdminComponentName,
@@ -60,7 +62,8 @@
@Nullable String timeZone,
long localTime,
@Nullable @SuppressLint("UseIcu") Locale locale,
- boolean deviceOwnerCanGrantSensorsPermissions) {
+ boolean deviceOwnerCanGrantSensorsPermissions,
+ @NonNull PersistableBundle adminExtras) {
this.mDeviceAdminComponentName = requireNonNull(deviceAdminComponentName);
this.mOwnerName = requireNonNull(ownerName);
this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled;
@@ -69,6 +72,7 @@
this.mLocale = locale;
this.mDeviceOwnerCanGrantSensorsPermissions =
deviceOwnerCanGrantSensorsPermissions;
+ this.mAdminExtras = adminExtras;
}
private FullyManagedDeviceProvisioningParams(
@@ -78,14 +82,16 @@
@Nullable String timeZone,
long localTime,
@Nullable String localeStr,
- boolean deviceOwnerCanGrantSensorsPermissions) {
+ boolean deviceOwnerCanGrantSensorsPermissions,
+ @Nullable PersistableBundle adminExtras) {
this(deviceAdminComponentName,
ownerName,
leaveAllSystemAppsEnabled,
timeZone,
localTime,
getLocale(localeStr),
- deviceOwnerCanGrantSensorsPermissions);
+ deviceOwnerCanGrantSensorsPermissions,
+ adminExtras);
}
@Nullable
@@ -151,6 +157,15 @@
}
/**
+ * Returns a copy of the admin extras bundle.
+ *
+ * @see DevicePolicyManager#EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
+ */
+ public @NonNull PersistableBundle getAdminExtras() {
+ return new PersistableBundle(mAdminExtras);
+ }
+
+ /**
* Logs the provisioning params using {@link DevicePolicyEventLogger}.
*
* @hide
@@ -188,6 +203,7 @@
@Nullable private Locale mLocale;
// Default to allowing control over sensor permission grants.
boolean mDeviceOwnerCanGrantSensorsPermissions = true;
+ @NonNull private PersistableBundle mAdminExtras;
/**
* Initialize a new {@link Builder} to construct a
@@ -262,6 +278,17 @@
}
/**
+ * Sets a {@link PersistableBundle} that contains admin-specific extras.
+ */
+ @NonNull
+ public Builder setAdminExtras(@NonNull PersistableBundle adminExtras) {
+ mAdminExtras = adminExtras != null
+ ? new PersistableBundle(adminExtras)
+ : new PersistableBundle();
+ return this;
+ }
+
+ /**
* Combines all of the attributes that have been set on this {@code Builder}
*
* @return a new {@link FullyManagedDeviceProvisioningParams} object.
@@ -275,7 +302,8 @@
mTimeZone,
mLocalTime,
mLocale,
- mDeviceOwnerCanGrantSensorsPermissions);
+ mDeviceOwnerCanGrantSensorsPermissions,
+ mAdminExtras);
}
}
@@ -298,6 +326,7 @@
+ ", mLocale=" + (mLocale == null ? "null" : mLocale)
+ ", mDeviceOwnerCanGrantSensorsPermissions="
+ mDeviceOwnerCanGrantSensorsPermissions
+ + ", mAdminExtras=" + mAdminExtras
+ '}';
}
@@ -310,6 +339,7 @@
dest.writeLong(mLocalTime);
dest.writeString(mLocale == null ? null : mLocale.toLanguageTag());
dest.writeBoolean(mDeviceOwnerCanGrantSensorsPermissions);
+ dest.writePersistableBundle(mAdminExtras);
}
@NonNull
@@ -324,6 +354,7 @@
long localtime = in.readLong();
String locale = in.readString();
boolean deviceOwnerCanGrantSensorsPermissions = in.readBoolean();
+ PersistableBundle adminExtras = in.readPersistableBundle();
return new FullyManagedDeviceProvisioningParams(
componentName,
@@ -332,7 +363,8 @@
timeZone,
localtime,
locale,
- deviceOwnerCanGrantSensorsPermissions);
+ deviceOwnerCanGrantSensorsPermissions,
+ adminExtras);
}
@Override
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 927ee0c..a7a51f8 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -272,6 +272,7 @@
int getLogoutUserId();
List<UserHandle> getSecondaryUsers(in ComponentName who);
void acknowledgeNewUserDisclaimer();
+ boolean isNewUserDisclaimerAcknowledged();
void enableSystemApp(in ComponentName admin, in String callerPackage, in String packageName);
int enableSystemAppWithIntent(in ComponentName admin, in String callerPackage, in Intent intent);
diff --git a/core/java/android/app/admin/ManagedProfileProvisioningParams.java b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
index ccbef73..f91d60a 100644
--- a/core/java/android/app/admin/ManagedProfileProvisioningParams.java
+++ b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
@@ -23,8 +23,10 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.ComponentName;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import android.stats.devicepolicy.DevicePolicyEnums;
/**
@@ -49,7 +51,7 @@
private final boolean mLeaveAllSystemAppsEnabled;
private final boolean mOrganizationOwnedProvisioning;
private final boolean mKeepAccountOnMigration;
-
+ @NonNull private final PersistableBundle mAdminExtras;
private ManagedProfileProvisioningParams(
@NonNull ComponentName profileAdminComponentName,
@@ -58,7 +60,8 @@
@Nullable Account accountToMigrate,
boolean leaveAllSystemAppsEnabled,
boolean organizationOwnedProvisioning,
- boolean keepAccountOnMigration) {
+ boolean keepAccountOnMigration,
+ @NonNull PersistableBundle adminExtras) {
this.mProfileAdminComponentName = requireNonNull(profileAdminComponentName);
this.mOwnerName = requireNonNull(ownerName);
this.mProfileName = profileName;
@@ -66,6 +69,7 @@
this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled;
this.mOrganizationOwnedProvisioning = organizationOwnedProvisioning;
this.mKeepAccountOnMigration = keepAccountOnMigration;
+ this.mAdminExtras = adminExtras;
}
/**
@@ -124,6 +128,15 @@
}
/**
+ * Returns a copy of the admin extras bundle.
+ *
+ * @see DevicePolicyManager#EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
+ */
+ public @NonNull PersistableBundle getAdminExtras() {
+ return new PersistableBundle(mAdminExtras);
+ }
+
+ /**
* Logs the provisioning params using {@link DevicePolicyEventLogger}.
*
* @hide
@@ -160,6 +173,7 @@
private boolean mLeaveAllSystemAppsEnabled;
private boolean mOrganizationOwnedProvisioning;
private boolean mKeepingAccountOnMigration;
+ @Nullable private PersistableBundle mAdminExtras;
/**
* Initialize a new {@link Builder) to construct a {@link ManagedProfileProvisioningParams}.
@@ -235,6 +249,17 @@
}
/**
+ * Sets a {@link Bundle} that contains admin-specific extras.
+ */
+ @NonNull
+ public Builder setAdminExtras(@NonNull PersistableBundle adminExtras) {
+ mAdminExtras = adminExtras != null
+ ? new PersistableBundle(adminExtras)
+ : new PersistableBundle();
+ return this;
+ }
+
+ /**
* Combines all of the attributes that have been set on this {@code Builder}.
*
* @return a new {@link ManagedProfileProvisioningParams} object.
@@ -248,7 +273,8 @@
mAccountToMigrate,
mLeaveAllSystemAppsEnabled,
mOrganizationOwnedProvisioning,
- mKeepingAccountOnMigration);
+ mKeepingAccountOnMigration,
+ mAdminExtras);
}
}
@@ -270,6 +296,7 @@
+ ", mLeaveAllSystemAppsEnabled=" + mLeaveAllSystemAppsEnabled
+ ", mOrganizationOwnedProvisioning=" + mOrganizationOwnedProvisioning
+ ", mKeepAccountOnMigration=" + mKeepAccountOnMigration
+ + ", mAdminExtras=" + mAdminExtras
+ '}';
}
@@ -282,6 +309,7 @@
dest.writeBoolean(mLeaveAllSystemAppsEnabled);
dest.writeBoolean(mOrganizationOwnedProvisioning);
dest.writeBoolean(mKeepAccountOnMigration);
+ dest.writePersistableBundle(mAdminExtras);
}
public static final @NonNull Creator<ManagedProfileProvisioningParams> CREATOR =
@@ -295,6 +323,7 @@
boolean leaveAllSystemAppsEnabled = in.readBoolean();
boolean organizationOwnedProvisioning = in.readBoolean();
boolean keepAccountMigrated = in.readBoolean();
+ PersistableBundle adminExtras = in.readPersistableBundle();
return new ManagedProfileProvisioningParams(
componentName,
@@ -303,7 +332,8 @@
account,
leaveAllSystemAppsEnabled,
organizationOwnedProvisioning,
- keepAccountMigrated);
+ keepAccountMigrated,
+ adminExtras);
}
@Override
diff --git a/core/java/android/app/admin/ParcelableResource.java b/core/java/android/app/admin/ParcelableResource.java
index dba3628..0b1b166 100644
--- a/core/java/android/app/admin/ParcelableResource.java
+++ b/core/java/android/app/admin/ParcelableResource.java
@@ -175,7 +175,7 @@
* <p>Returns the default drawable by calling the {@code defaultDrawableLoader} if the updated
* drawable was not found or could not be loaded.</p>
*/
- @NonNull
+ @Nullable
public Drawable getDrawable(
Context context,
int density,
@@ -200,7 +200,7 @@
* <p>Returns the default string by calling {@code defaultStringLoader} if the updated
* string was not found or could not be loaded.</p>
*/
- @NonNull
+ @Nullable
public String getString(
Context context,
@NonNull Callable<String> defaultStringLoader) {
@@ -267,17 +267,11 @@
/**
* returns the {@link Drawable} loaded from calling {@code defaultDrawableLoader}.
*/
- @NonNull
+ @Nullable
public static Drawable loadDefaultDrawable(@NonNull Callable<Drawable> defaultDrawableLoader) {
try {
Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null");
-
- Drawable drawable = defaultDrawableLoader.call();
- Objects.requireNonNull(drawable, "defaultDrawable can't be null");
-
- return drawable;
- } catch (NullPointerException rethrown) {
- throw rethrown;
+ return defaultDrawableLoader.call();
} catch (Exception e) {
throw new RuntimeException("Couldn't load default drawable: ", e);
}
@@ -286,17 +280,11 @@
/**
* returns the {@link String} loaded from calling {@code defaultStringLoader}.
*/
- @NonNull
+ @Nullable
public static String loadDefaultString(@NonNull Callable<String> defaultStringLoader) {
try {
Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null");
-
- String string = defaultStringLoader.call();
- Objects.requireNonNull(string, "defaultString can't be null");
-
- return string;
- } catch (NullPointerException rethrown) {
- throw rethrown;
+ return defaultStringLoader.call();
} catch (Exception e) {
throw new RuntimeException("Couldn't load default string: ", e);
}
diff --git a/core/java/android/app/wallpapereffectsgeneration/CameraAttributes.java b/core/java/android/app/wallpapereffectsgeneration/CameraAttributes.java
new file mode 100644
index 0000000..dfbc7a4
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/CameraAttributes.java
@@ -0,0 +1,301 @@
+/*
+ * 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 android.app.wallpapereffectsgeneration;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Representing the position and other parameters of camera of a single frame.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CameraAttributes implements Parcelable {
+ /**
+ * The location of the anchor within the 3D scene.
+ * Expecting 3 floats representing the x, y, z coordinates
+ * of the anchor point.
+ */
+ @NonNull
+ private float[] mAnchorPointInWorldSpace;
+ /**
+ * Where the anchor point should project to in the output image.
+ * Expecting 2 floats representing the u,v coordinates of the
+ * anchor point.
+ */
+ @NonNull
+ private float[] mAnchorPointInOutputUvSpace;
+ /**
+ * Specifies the amount of yaw orbit rotation the camera should perform
+ * around the anchor point in world space.
+ */
+ private float mCameraOrbitYawDegrees;
+ /**
+ * Specifies the amount of pitch orbit rotation the camera should perform
+ * around the anchor point in world space.
+ */
+ private float mCameraOrbitPitchDegrees;
+ /**
+ * Specifies by how much the camera should be placed towards the anchor
+ * point in world space, which is also called dolly distance.
+ */
+ private float mDollyDistanceInWorldSpace;
+ /**
+ * Specifies the vertical fov degrees of the virtual image.
+ */
+ private float mVerticalFovDegrees;
+ /**
+ * The frustum of near plane.
+ */
+ private float mFrustumNearInWorldSpace;
+ /**
+ * The frustum of far plane.
+ */
+ private float mFrustumFarInWorldSpace;
+
+ private CameraAttributes(Parcel in) {
+ this.mCameraOrbitYawDegrees = in.readFloat();
+ this.mCameraOrbitPitchDegrees = in.readFloat();
+ this.mDollyDistanceInWorldSpace = in.readFloat();
+ this.mVerticalFovDegrees = in.readFloat();
+ this.mFrustumNearInWorldSpace = in.readFloat();
+ this.mFrustumFarInWorldSpace = in.readFloat();
+ this.mAnchorPointInWorldSpace = in.createFloatArray();
+ this.mAnchorPointInOutputUvSpace = in.createFloatArray();
+ }
+
+ private CameraAttributes(float[] anchorPointInWorldSpace, float[] anchorPointInOutputUvSpace,
+ float cameraOrbitYawDegrees, float cameraOrbitPitchDegrees,
+ float dollyDistanceInWorldSpace,
+ float verticalFovDegrees, float frustumNearInWorldSpace, float frustumFarInWorldSpace) {
+ mAnchorPointInWorldSpace = anchorPointInWorldSpace;
+ mAnchorPointInOutputUvSpace = anchorPointInOutputUvSpace;
+ mCameraOrbitYawDegrees = cameraOrbitYawDegrees;
+ mCameraOrbitPitchDegrees = cameraOrbitPitchDegrees;
+ mDollyDistanceInWorldSpace = dollyDistanceInWorldSpace;
+ mVerticalFovDegrees = verticalFovDegrees;
+ mFrustumNearInWorldSpace = frustumNearInWorldSpace;
+ mFrustumFarInWorldSpace = frustumFarInWorldSpace;
+ }
+
+ /**
+ * Get the location of the anchor within the 3D scene. The response float array contains
+ * 3 floats representing the x, y, z coordinates
+ */
+ @NonNull
+ public float[] getAnchorPointInWorldSpace() {
+ return mAnchorPointInWorldSpace;
+ }
+
+ /**
+ * Get where the anchor point should project to in the output image. The response float
+ * array contains 2 floats representing the u,v coordinates of the anchor point.
+ */
+ @NonNull
+ public float[] getAnchorPointInOutputUvSpace() {
+ return mAnchorPointInOutputUvSpace;
+ }
+
+ /**
+ * Get the camera yaw orbit rotation.
+ */
+ public float getCameraOrbitYawDegrees() {
+ return mCameraOrbitYawDegrees;
+ }
+
+ /**
+ * Get the camera pitch orbit rotation.
+ */
+ public float getCameraOrbitPitchDegrees() {
+ return mCameraOrbitPitchDegrees;
+ }
+
+ /**
+ * Get how many units the camera should be placed towards the anchor point in world space.
+ */
+ public float getDollyDistanceInWorldSpace() {
+ return mDollyDistanceInWorldSpace;
+ }
+
+ /**
+ * Get the camera vertical fov degrees.
+ */
+ public float getVerticalFovDegrees() {
+ return mVerticalFovDegrees;
+ }
+
+ /**
+ * Get the frustum in near plane.
+ */
+ public float getFrustumNearInWorldSpace() {
+ return mFrustumNearInWorldSpace;
+ }
+
+ /**
+ * Get the frustum in far plane.
+ */
+ public float getFrustumFarInWorldSpace() {
+ return mFrustumFarInWorldSpace;
+ }
+
+ @NonNull
+ public static final Creator<CameraAttributes> CREATOR = new Creator<CameraAttributes>() {
+ @Override
+ public CameraAttributes createFromParcel(Parcel in) {
+ return new CameraAttributes(in);
+ }
+
+ @Override
+ public CameraAttributes[] newArray(int size) {
+ return new CameraAttributes[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeFloat(mCameraOrbitYawDegrees);
+ out.writeFloat(mCameraOrbitPitchDegrees);
+ out.writeFloat(mDollyDistanceInWorldSpace);
+ out.writeFloat(mVerticalFovDegrees);
+ out.writeFloat(mFrustumNearInWorldSpace);
+ out.writeFloat(mFrustumFarInWorldSpace);
+ out.writeFloatArray(mAnchorPointInWorldSpace);
+ out.writeFloatArray(mAnchorPointInOutputUvSpace);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Builder for {@link CameraAttributes}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ @NonNull
+ private float[] mAnchorPointInWorldSpace;
+ @NonNull
+ private float[] mAnchorPointInOutputUvSpace;
+ private float mCameraOrbitYawDegrees;
+ private float mCameraOrbitPitchDegrees;
+ private float mDollyDistanceInWorldSpace;
+ private float mVerticalFovDegrees;
+ private float mFrustumNearInWorldSpace;
+ private float mFrustumFarInWorldSpace;
+
+ /**
+ * Constructor with anchor point in world space and anchor point in output image
+ * space.
+ *
+ * @param anchorPointInWorldSpace the location of the anchor within the 3D scene. The
+ * float array contains 3 floats representing the x, y, z coordinates.
+ * @param anchorPointInOutputUvSpace where the anchor point should project to in the
+ * output image. The float array contains 2 floats representing the u,v coordinates
+ * of the anchor point.
+ *
+ * @hide
+ */
+ @SystemApi
+ public Builder(@NonNull float[] anchorPointInWorldSpace,
+ @NonNull float[] anchorPointInOutputUvSpace) {
+ mAnchorPointInWorldSpace = anchorPointInWorldSpace;
+ mAnchorPointInOutputUvSpace = anchorPointInOutputUvSpace;
+ }
+
+ /**
+ * Sets the camera orbit yaw rotation.
+ */
+ @NonNull
+ public Builder setCameraOrbitYawDegrees(
+ @FloatRange(from = -180.0f, to = 180.0f) float cameraOrbitYawDegrees) {
+ mCameraOrbitYawDegrees = cameraOrbitYawDegrees;
+ return this;
+ }
+
+ /**
+ * Sets the camera orbit pitch rotation.
+ */
+ @NonNull
+ public Builder setCameraOrbitPitchDegrees(
+ @FloatRange(from = -90.0f, to = 90.0f) float cameraOrbitPitchDegrees) {
+ mCameraOrbitPitchDegrees = cameraOrbitPitchDegrees;
+ return this;
+ }
+
+ /**
+ * Sets the camera dolly distance.
+ */
+ @NonNull
+ public Builder setDollyDistanceInWorldSpace(float dollyDistanceInWorldSpace) {
+ mDollyDistanceInWorldSpace = dollyDistanceInWorldSpace;
+ return this;
+ }
+
+ /**
+ * Sets the camera vertical fov degree.
+ */
+ @NonNull
+ public Builder setVerticalFovDegrees(
+ @FloatRange(from = 0.0f, to = 180.0f, fromInclusive = false)
+ float verticalFovDegrees) {
+ mVerticalFovDegrees = verticalFovDegrees;
+ return this;
+ }
+
+ /**
+ * Sets the frustum in near plane.
+ */
+ @NonNull
+ public Builder setFrustumNearInWorldSpace(
+ @FloatRange(from = 0.0f) float frustumNearInWorldSpace) {
+ mFrustumNearInWorldSpace = frustumNearInWorldSpace;
+ return this;
+ }
+
+ /**
+ * Sets the frustum in far plane.
+ */
+ @NonNull
+ public Builder setFrustumFarInWorldSpace(
+ @FloatRange(from = 0.0f) float frustumFarInWorldSpace) {
+ mFrustumFarInWorldSpace = frustumFarInWorldSpace;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link CameraAttributes} instance.
+ */
+ @NonNull
+ public CameraAttributes build() {
+ return new CameraAttributes(mAnchorPointInWorldSpace,
+ mAnchorPointInOutputUvSpace,
+ mCameraOrbitYawDegrees,
+ mCameraOrbitPitchDegrees,
+ mDollyDistanceInWorldSpace,
+ mVerticalFovDegrees,
+ mFrustumNearInWorldSpace,
+ mFrustumFarInWorldSpace);
+ }
+ }
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.aidl
similarity index 79%
rename from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
rename to core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.aidl
index cb602d79..2347746 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.aidl
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2021, The Android Open Source Project
+ * 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.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.net;
+package android.app.wallpapereffectsgeneration;
-parcelable NetworkStateSnapshot;
+parcelable CinematicEffectRequest;
\ No newline at end of file
diff --git a/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.java b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.java
new file mode 100644
index 0000000..f2e3313
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.java
@@ -0,0 +1,115 @@
+/*
+ * 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 android.app.wallpapereffectsgeneration;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A {@link CinematicEffectRequest} is the data class having all the information
+ * passed to wallpaper effects generation service.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CinematicEffectRequest implements Parcelable {
+ /**
+ * Unique id of a cienmatic effect generation task.
+ */
+ @NonNull
+ private String mTaskId;
+
+ /**
+ * The bitmap to generate cinematic effect from.
+ */
+ @NonNull
+ private Bitmap mBitmap;
+
+ private CinematicEffectRequest(Parcel in) {
+ this.mTaskId = in.readString();
+ this.mBitmap = Bitmap.CREATOR.createFromParcel(in);
+ }
+
+ /**
+ * Constructor with task id and bitmap.
+ */
+ public CinematicEffectRequest(@NonNull String taskId, @NonNull Bitmap bitmap) {
+ mTaskId = taskId;
+ mBitmap = bitmap;
+ }
+
+ /**
+ * Returns the task id.
+ */
+ @NonNull
+ public String getTaskId() {
+ return mTaskId;
+ }
+
+ /**
+ * Returns the bitmap of this request.
+ */
+ @NonNull
+ public Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CinematicEffectRequest that = (CinematicEffectRequest) o;
+ return mTaskId.equals(that.mTaskId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTaskId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeString(mTaskId);
+ mBitmap.writeToParcel(out, flags);
+ }
+
+ @NonNull
+ public static final Creator<CinematicEffectRequest> CREATOR =
+ new Creator<CinematicEffectRequest>() {
+ @Override
+ public CinematicEffectRequest createFromParcel(Parcel in) {
+ return new CinematicEffectRequest(in);
+ }
+
+ @Override
+ public CinematicEffectRequest[] newArray(int size) {
+ return new CinematicEffectRequest[size];
+ }
+ };
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.aidl
similarity index 79%
copy from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
copy to core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.aidl
index cb602d79..1bd1e1e 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.aidl
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2021, The Android Open Source Project
+ * 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.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.net;
+ package android.app.wallpapereffectsgeneration;
-parcelable NetworkStateSnapshot;
+ parcelable CinematicEffectResponse;
\ No newline at end of file
diff --git a/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.java b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.java
new file mode 100644
index 0000000..1254794
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.java
@@ -0,0 +1,299 @@
+/*
+ * 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 android.app.wallpapereffectsgeneration;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@link CinematicEffectResponse} include textured meshes
+ * and camera attributes of key frames.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CinematicEffectResponse implements Parcelable {
+ /** @hide */
+ @IntDef(prefix = {"CINEMATIC_EFFECT_STATUS_"},
+ value = {CINEMATIC_EFFECT_STATUS_UNKNOWN,
+ CINEMATIC_EFFECT_STATUS_OK,
+ CINEMATIC_EFFECT_STATUS_ERROR,
+ CINEMATIC_EFFECT_STATUS_NOT_READY,
+ CINEMATIC_EFFECT_STATUS_PENDING,
+ CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CinematicEffectStatusCode {}
+
+ /** Cinematic effect generation unknown status. */
+ public static final int CINEMATIC_EFFECT_STATUS_UNKNOWN = 0;
+ /** Cinematic effect generation success. */
+ public static final int CINEMATIC_EFFECT_STATUS_OK = 1;
+ /** Cinematic effect generation failure. */
+ public static final int CINEMATIC_EFFECT_STATUS_ERROR = 2;
+ /** Service not ready for cinematic effect generation. */
+ public static final int CINEMATIC_EFFECT_STATUS_NOT_READY = 3;
+ /** Cienmatic effect generation process is pending. */
+ public static final int CINEMATIC_EFFECT_STATUS_PENDING = 4;
+ /** Too manay requests for server to handle. */
+ public static final int CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS = 5;
+
+ /** @hide */
+ @IntDef(prefix = {"IMAGE_CONTENT_TYPE_"},
+ value = {IMAGE_CONTENT_TYPE_UNKNOWN,
+ IMAGE_CONTENT_TYPE_PEOPLE_PORTRAIT,
+ IMAGE_CONTENT_TYPE_LANDSCAPE,
+ IMAGE_CONTENT_TYPE_OTHER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ImageContentType {}
+
+ /** Image content unknown. */
+ public static final int IMAGE_CONTENT_TYPE_UNKNOWN = 0;
+ /** Image content is people portrait. */
+ public static final int IMAGE_CONTENT_TYPE_PEOPLE_PORTRAIT = 1;
+ /** Image content is landscape. */
+ public static final int IMAGE_CONTENT_TYPE_LANDSCAPE = 2;
+ /** Image content is doesn't belong to other types. */
+ public static final int IMAGE_CONTENT_TYPE_OTHER = 3;
+
+
+ @CinematicEffectStatusCode
+ private int mStatusCode;
+
+ /** The id of the cinematic effect generation task. */
+ @NonNull
+ private String mTaskId;
+
+ @ImageContentType
+ private int mImageContentType;
+
+ /** The textured mesh required to render cinematic effect. */
+ @NonNull
+ private List<TexturedMesh> mTexturedMeshes;
+
+ /** The start camera position for animation. */
+ @Nullable
+ private CameraAttributes mStartKeyFrame;
+
+ /** The end camera position for animation. */
+ @Nullable
+ private CameraAttributes mEndKeyFrame;
+
+ private CinematicEffectResponse(Parcel in) {
+ mStatusCode = in.readInt();
+ mTaskId = in.readString();
+ mImageContentType = in.readInt();
+ mTexturedMeshes = new ArrayList<TexturedMesh>();
+ in.readTypedList(mTexturedMeshes, TexturedMesh.CREATOR);
+ mStartKeyFrame = in.readTypedObject(CameraAttributes.CREATOR);
+ mEndKeyFrame = in.readTypedObject(CameraAttributes.CREATOR);
+ }
+
+ private CinematicEffectResponse(@CinematicEffectStatusCode int statusCode,
+ String taskId,
+ @ImageContentType int imageContentType,
+ List<TexturedMesh> texturedMeshes,
+ CameraAttributes startKeyFrame,
+ CameraAttributes endKeyFrame) {
+ mStatusCode = statusCode;
+ mTaskId = taskId;
+ mImageContentType = imageContentType;
+ mStartKeyFrame = startKeyFrame;
+ mEndKeyFrame = endKeyFrame;
+ mTexturedMeshes = texturedMeshes;
+ }
+
+ /** Gets the cinematic effect generation status code. */
+ @CinematicEffectStatusCode
+ public int getStatusCode() {
+ return mStatusCode;
+ }
+
+ /** Get the task id. */
+ @NonNull
+ public String getTaskId() {
+ return mTaskId;
+ }
+
+ /**
+ * Get the image content type, which briefly classifies what's
+ * the content of image, like people portrait, landscape etc.
+ */
+ @ImageContentType
+ public int getImageContentType() {
+ return mImageContentType;
+ }
+
+ /** Get the textured meshes. */
+ @NonNull
+ public List<TexturedMesh> getTexturedMeshes() {
+ return mTexturedMeshes;
+ }
+
+ /**
+ * Get the camera attributes (position info and other parameters, see docs of
+ * {@link CameraAttributes}) of the start key frame on the animation path.
+ */
+ @Nullable
+ public CameraAttributes getStartKeyFrame() {
+ return mStartKeyFrame;
+ }
+
+ /**
+ * Get the camera attributes (position info and other parameters, see docs of
+ * {@link CameraAttributes}) of the end key frame on the animation path.
+ */
+ @Nullable
+ public CameraAttributes getEndKeyFrame() {
+ return mEndKeyFrame;
+ }
+
+ @NonNull
+ public static final Creator<CinematicEffectResponse> CREATOR =
+ new Creator<CinematicEffectResponse>() {
+ @Override
+ public CinematicEffectResponse createFromParcel(Parcel in) {
+ return new CinematicEffectResponse(in);
+ }
+
+ @Override
+ public CinematicEffectResponse[] newArray(int size) {
+ return new CinematicEffectResponse[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mStatusCode);
+ out.writeString(mTaskId);
+ out.writeInt(mImageContentType);
+ out.writeTypedList(mTexturedMeshes, flags);
+ out.writeTypedObject(mStartKeyFrame, flags);
+ out.writeTypedObject(mEndKeyFrame, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CinematicEffectResponse that = (CinematicEffectResponse) o;
+ return mTaskId.equals(that.mTaskId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTaskId);
+ }
+ /**
+ * Builder of {@link CinematicEffectResponse}
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ @CinematicEffectStatusCode
+ private int mStatusCode;
+ @NonNull
+ private String mTaskId;
+ @ImageContentType
+ private int mImageContentType;
+ @NonNull
+ private List<TexturedMesh> mTexturedMeshes;
+ @Nullable
+ private CameraAttributes mStartKeyFrame;
+ @Nullable
+ private CameraAttributes mEndKeyFrame;
+
+ /**
+ * Constructor with task id and status code.
+ *
+ * @hide
+ */
+ @SystemApi
+ public Builder(@CinematicEffectStatusCode int statusCode, @NonNull String taskId) {
+ mStatusCode = statusCode;
+ mTaskId = taskId;
+ }
+
+ /**
+ * Sets the image content type.
+ */
+ @NonNull
+ public Builder setImageContentType(@ImageContentType int imageContentType) {
+ mImageContentType = imageContentType;
+ return this;
+ }
+
+
+ /**
+ * Sets the textured meshes.
+ */
+ @NonNull
+ public Builder setTexturedMeshes(@NonNull List<TexturedMesh> texturedMeshes) {
+ mTexturedMeshes = texturedMeshes;
+ return this;
+ }
+
+ /**
+ * Sets start key frame.
+ */
+ @NonNull
+ public Builder setStartKeyFrame(@Nullable CameraAttributes startKeyFrame) {
+ mStartKeyFrame = startKeyFrame;
+ return this;
+ }
+
+ /**
+ * Sets end key frame.
+ */
+ @NonNull
+ public Builder setEndKeyFrame(@Nullable CameraAttributes endKeyFrame) {
+ mEndKeyFrame = endKeyFrame;
+ return this;
+ }
+
+ /**
+ * Builds a {@link CinematicEffectResponse} instance.
+ */
+ @NonNull
+ public CinematicEffectResponse build() {
+ if (mTexturedMeshes == null) {
+ // Place holder because build doesn't allow list to be nullable.
+ mTexturedMeshes = new ArrayList<>(0);
+ }
+ return new CinematicEffectResponse(mStatusCode, mTaskId, mImageContentType,
+ mTexturedMeshes, mStartKeyFrame, mEndKeyFrame);
+ }
+ }
+}
diff --git a/core/java/android/app/wallpapereffectsgeneration/ICinematicEffectListener.aidl b/core/java/android/app/wallpapereffectsgeneration/ICinematicEffectListener.aidl
new file mode 100644
index 0000000..c1a698d
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/ICinematicEffectListener.aidl
@@ -0,0 +1,30 @@
+/**
+ * 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 android.app.wallpapereffectsgeneration;
+
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+
+
+/**
+ * Callback used by system server to notify invoker of {@link WallpaperEffectsGenerationMAnager}
+ * of the cinematic effect generation result.
+ *
+ * @hide
+ */
+oneway interface ICinematicEffectListener {
+ void onCinematicEffectGenerated(in CinematicEffectResponse response);
+}
\ No newline at end of file
diff --git a/core/java/android/app/wallpapereffectsgeneration/IWallpaperEffectsGenerationManager.aidl b/core/java/android/app/wallpapereffectsgeneration/IWallpaperEffectsGenerationManager.aidl
new file mode 100644
index 0000000..706a89c
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/IWallpaperEffectsGenerationManager.aidl
@@ -0,0 +1,34 @@
+/**
+ * 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 android.app.wallpapereffectsgeneration;
+
+import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+import android.app.wallpapereffectsgeneration.ICinematicEffectListener;
+
+/**
+ * Used by {@link android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager}
+ * to to generate effects.
+ *
+ * @hide
+ */
+oneway interface IWallpaperEffectsGenerationManager {
+ void generateCinematicEffect(in CinematicEffectRequest request,
+ in ICinematicEffectListener listener);
+
+ void returnCinematicEffectResponse(in CinematicEffectResponse response);
+}
\ No newline at end of file
diff --git a/core/java/android/app/wallpapereffectsgeneration/TexturedMesh.java b/core/java/android/app/wallpapereffectsgeneration/TexturedMesh.java
new file mode 100644
index 0000000..630de45
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/TexturedMesh.java
@@ -0,0 +1,273 @@
+/*
+ * 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 android.app.wallpapereffectsgeneration;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The textured mesh representation, including a texture (bitmap) to sample from when rendering,
+ * and a mesh consisting of primitives such as triangles. The mesh is represented by an indices
+ * array describing the set of primitives in the mesh, and a vertices array that the indices
+ * refer to.
+ *
+ * @hide
+ */
+@SystemApi
+public final class TexturedMesh implements Parcelable {
+ /**
+ * The texture to sample from when rendering mesh.
+ */
+ @NonNull
+ private Bitmap mBitmap;
+
+ /**
+ * The set of primitives as pointers into the vertices.
+ */
+ @NonNull
+ private int[] mIndices;
+
+ /**
+ * The specific vertices that the indices refer to.
+ */
+ @NonNull
+ private float[] mVertices;
+
+ /** @hide */
+ @IntDef(prefix = {"INDICES_LAYOUT_"}, value = {
+ INDICES_LAYOUT_UNDEFINED,
+ INDICES_LAYOUT_TRIANGLES})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface IndicesLayoutType {
+ }
+
+ /** Undefined indices layout */
+ public static final int INDICES_LAYOUT_UNDEFINED = 0;
+ /**
+ * Indices layout is triangle. Vertices are grouped into 3 and each
+ * group forms a triangle.
+ */
+ public static final int INDICES_LAYOUT_TRIANGLES = 1;
+
+ @IndicesLayoutType
+ private int mIndicesLayoutType;
+
+ /** @hide */
+ @IntDef(prefix = {"VERTICES_LAYOUT_"}, value = {
+ VERTICES_LAYOUT_UNDEFINED,
+ VERTICES_LAYOUT_POSITION3_UV2})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface VerticesLayoutType {
+ }
+
+ /**
+ * Undefined vertices layout.
+ */
+ public static final int VERTICES_LAYOUT_UNDEFINED = 0;
+ /**
+ * The vertices array uses 5 numbers to represent a point, in the format
+ * of [x1, y1, z1, u1, v1, x2, y2, z2, u2, v2, ...].
+ */
+ public static final int VERTICES_LAYOUT_POSITION3_UV2 = 1;
+
+ @VerticesLayoutType
+ private int mVerticesLayoutType;
+
+ private TexturedMesh(Parcel in) {
+ this.mIndicesLayoutType = in.readInt();
+ this.mVerticesLayoutType = in.readInt();
+ this.mBitmap = in.readTypedObject(Bitmap.CREATOR);
+ Parcel data = Parcel.obtain();
+ try {
+ byte[] bytes = in.readBlob();
+ data.unmarshall(bytes, 0, bytes.length);
+ data.setDataPosition(0);
+ this.mIndices = data.createIntArray();
+ this.mVertices = data.createFloatArray();
+ } finally {
+ data.recycle();
+ }
+ }
+
+ private TexturedMesh(@NonNull Bitmap bitmap, @NonNull int[] indices,
+ @NonNull float[] vertices, @IndicesLayoutType int indicesLayoutType,
+ @VerticesLayoutType int verticesLayoutType) {
+ mBitmap = bitmap;
+ mIndices = indices;
+ mVertices = vertices;
+ mIndicesLayoutType = indicesLayoutType;
+ mVerticesLayoutType = verticesLayoutType;
+ }
+
+ /** Get the bitmap, which is the texture to sample from when rendering. */
+ @NonNull
+ public Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ /**
+ * Get the indices as pointers to the vertices array. Depending on the getIndicesLayoutType(),
+ * the primitives may have different shapes. For example, with INDICES_LAYOUT_TRIANGLES,
+ * indices 0, 1, 2 forms a triangle, indices 3, 4, 5 form another triangle.
+ */
+ @NonNull
+ public int[] getIndices() {
+ return mIndices;
+ }
+
+ /**
+ * Get the vertices that the index array refers to. Depending on the getVerticesLayoutType()
+ * result, the vertices array can represent different per-vertex coordinates. For example,
+ * with VERTICES_LAYOUT_POSITION3_UV2 type, vertices are in the format of
+ * [x1, y1, z1, u1, v1, x2, y2, z2, u2, v2, ...].
+ */
+ @NonNull
+ public float[] getVertices() {
+ return mVertices;
+ }
+
+ /** Get the indices layout type. */
+ @IndicesLayoutType
+ @NonNull
+ public int getIndicesLayoutType() {
+ return mIndicesLayoutType;
+ }
+
+ /** Get the indices layout type. */
+ @VerticesLayoutType
+ @NonNull
+ public int getVerticesLayoutType() {
+ return mVerticesLayoutType;
+ }
+
+ @NonNull
+ public static final Creator<TexturedMesh> CREATOR = new Creator<TexturedMesh>() {
+ @Override
+ public TexturedMesh createFromParcel(Parcel in) {
+ return new TexturedMesh(in);
+ }
+
+ @Override
+ public TexturedMesh[] newArray(int size) {
+ return new TexturedMesh[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mIndicesLayoutType);
+ out.writeInt(mVerticesLayoutType);
+ out.writeTypedObject(mBitmap, flags);
+
+ // Indices and vertices can reach 5MB. Write the data as a Blob,
+ // which will be written to ashmem if too large.
+ Parcel data = Parcel.obtain();
+ try {
+ data.writeIntArray(mIndices);
+ data.writeFloatArray(mVertices);
+ out.writeBlob(data.marshall());
+ } finally {
+ data.recycle();
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * A builder for {@link TexturedMesh}
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ private Bitmap mBitmap;
+ private int[] mIndices;
+ private float[] mVertices;
+ @IndicesLayoutType
+ private int mIndicesLayoutType;
+ @VerticesLayoutType
+ private int mVerticesLayouttype;
+
+ /**
+ * Constructor with bitmap.
+ *
+ * @hide
+ */
+ @SystemApi
+ public Builder(@NonNull Bitmap bitmap) {
+ mBitmap = bitmap;
+ }
+
+ /**
+ * Set the required indices. The indices should represent the primitives. For example,
+ * with INDICES_LAYOUT_TRIANGLES, indices 0, 1, 2 forms a triangle, indices 3, 4, 5
+ * form another triangle.
+ */
+ @NonNull
+ public Builder setIndices(@NonNull int[] indices) {
+ mIndices = indices;
+ return this;
+ }
+
+ /**
+ * Set the required vertices. The vertices array should represent per-vertex coordinates.
+ * For example, with VERTICES_LAYOUT_POSITION3_UV2 type, vertices are in the format of
+ * [x1, y1, z1, u1, v1, x2, y2, z2, u2, v2, ...].
+ *
+ */
+ @NonNull
+ public Builder setVertices(@NonNull float[] vertices) {
+ mVertices = vertices;
+ return this;
+ }
+
+ /**
+ * Set the required indices layout type.
+ */
+ @NonNull
+ public Builder setIndicesLayoutType(@IndicesLayoutType int indicesLayoutType) {
+ mIndicesLayoutType = indicesLayoutType;
+ return this;
+ }
+
+ /**
+ * Set the required vertices layout type.
+ */
+ @NonNull
+ public Builder setVerticesLayoutType(@VerticesLayoutType int verticesLayoutype) {
+ mVerticesLayouttype = verticesLayoutype;
+ return this;
+ }
+
+ /** Builds a TexturedMesh based on the given parameters. */
+ @NonNull
+ public TexturedMesh build() {
+ return new TexturedMesh(mBitmap, mIndices, mVertices, mIndicesLayoutType,
+ mVerticesLayouttype);
+ }
+ }
+}
diff --git a/core/java/android/app/wallpapereffectsgeneration/WallpaperEffectsGenerationManager.java b/core/java/android/app/wallpapereffectsgeneration/WallpaperEffectsGenerationManager.java
new file mode 100644
index 0000000..5a1d27d
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/WallpaperEffectsGenerationManager.java
@@ -0,0 +1,114 @@
+/*
+ * 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 android.app.wallpapereffectsgeneration;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A {@link WallpaperEffectsGenerationManager} is the class that passes wallpaper effects
+ * generation requests to wallpaper effect generation service. For example, create a cinematic
+ * and render a cinematic live wallpaper with the response.
+ *
+ * Usage:
+ * <pre>{@code
+ * mWallpaperEffectsGenerationManager =
+ * context.getSystemService(WallpaperEffectsGenerationManager.class);
+ * mWallpaperEffectsGenerationManager.
+ * generateCinematicEffect(cinematicEffectRequest, response->{
+ * // proceed cinematic effect response.
+ * });
+ * }</pre>
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.WALLPAPER_EFFECTS_GENERATION_SERVICE)
+public final class WallpaperEffectsGenerationManager {
+ /**
+ * Interface for the cinematic effect listener.
+ */
+ public interface CinematicEffectListener {
+ /**
+ * Async call when the cinematic effect response is generated.
+ * Client needs to check the status code of {@link CinematicEffectResponse}
+ * to determine if the effect generation is successful.
+ *
+ * @param response The generated cinematic effect response.
+ */
+ void onCinematicEffectGenerated(@NonNull CinematicEffectResponse response);
+ }
+
+ private final IWallpaperEffectsGenerationManager mService;
+
+ /** @hide */
+ public WallpaperEffectsGenerationManager(
+ @NonNull IWallpaperEffectsGenerationManager service) {
+ mService = service;
+ }
+
+ /**
+ * Execute a {@link android.app.wallpapereffectsgeneration.CinematicEffectRequest} from
+ * the given parameters to the wallpaper effects generation service. After the cinematic
+ * effect response is ready, the given listener is invoked by the system with the response.
+ * The listener may never receive a callback if unexpected error happened when proceeding
+ * request.
+ *
+ * @param request request to generate cinematic effect.
+ * @param executor where the listener is invoked.
+ * @param listener listener invoked when the cinematic effect response is available.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION)
+ public void generateCinematicEffect(@NonNull CinematicEffectRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull CinematicEffectListener listener) {
+ try {
+ mService.generateCinematicEffect(request,
+ new CinematicEffectListenerWrapper(listener, executor));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static final class CinematicEffectListenerWrapper
+ extends ICinematicEffectListener.Stub {
+ @NonNull
+ private final CinematicEffectListener mListener;
+ @NonNull
+ private final Executor mExecutor;
+
+ CinematicEffectListenerWrapper(@NonNull CinematicEffectListener listener,
+ @NonNull Executor executor) {
+ mListener = listener;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onCinematicEffectGenerated(CinematicEffectResponse response) {
+ mExecutor.execute(() -> mListener.onCinematicEffectGenerated(response));
+ }
+ }
+}
diff --git a/core/java/android/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java
index 941e9e2e..4e00da1 100644
--- a/core/java/android/attention/AttentionManagerInternal.java
+++ b/core/java/android/attention/AttentionManagerInternal.java
@@ -46,6 +46,25 @@
*/
public abstract void cancelAttentionCheck(AttentionCallbackInternal callback);
+ /**
+ * Requests the continuous updates of proximity signal via the provided callback,
+ * until the given callback is unregistered. Currently, AttentionManagerService only
+ * anticipates one client and updates one client at a time. If a new client wants to
+ * onboard to receiving Proximity updates, please make a feature request to make proximity
+ * feature multi-client before depending on this feature.
+ *
+ * @param callback a callback that receives the proximity updates
+ * @return {@code true} if the registration should succeed.
+ */
+ public abstract boolean onStartProximityUpdates(ProximityCallbackInternal callback);
+
+ /**
+ * Requests to stop providing continuous updates until the callback is registered.
+ *
+ * @param callback a callback that was used in {@link #onStartProximityUpdates}
+ */
+ public abstract void onStopProximityUpdates(ProximityCallbackInternal callback);
+
/** Internal interface for attention callback. */
public abstract static class AttentionCallbackInternal {
/**
@@ -64,4 +83,13 @@
*/
public abstract void onFailure(int error);
}
+
+ /** Internal interface for proximity callback. */
+ public abstract static class ProximityCallbackInternal {
+ /**
+ * @param distance the estimated distance of the user (in meter)
+ * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive.
+ */
+ public abstract void onProximityUpdate(double distance);
+ }
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 2ddfeb4..1d0f7c0 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.ComponentName;
import android.os.Parcel;
@@ -106,11 +107,13 @@
}
/**
- * Returns the set of activities allowed to be streamed, or {@code null} if this is not set.
+ * Returns the set of activities allowed to be streamed, or {@code null} if all activities are
+ * allowed, except the ones explicitly blocked.
*
* @see Builder#setAllowedActivities(Set)
- * @hide // TODO(b/194949534): Unhide this API
*/
+ // Null and empty have different semantics - Null allows all activities to be streamed
+ @SuppressLint("NullableCollection")
@Nullable
public Set<ComponentName> getAllowedActivities() {
if (mAllowedActivities == null) {
@@ -120,12 +123,13 @@
}
/**
- * Returns the set of activities that are blocked from streaming, or {@code null} if this is not
- * set.
+ * Returns the set of activities that are blocked from streaming, or {@code null} to indicate
+ * that all activities in {@link #getAllowedActivities} are allowed.
*
* @see Builder#setBlockedActivities(Set)
- * @hide // TODO(b/194949534): Unhide this API
*/
+ // Allowing null to enforce that at most one of allowed / blocked activities can be non-null
+ @SuppressLint("NullableCollection")
@Nullable
public Set<ComponentName> getBlockedActivities() {
if (mBlockedActivities == null) {
@@ -255,8 +259,10 @@
*
* @param allowedActivities A set of activity {@link ComponentName} allowed to be launched
* in the virtual device.
- * @hide // TODO(b/194949534): Unhide this API
*/
+ // Null and empty have different semantics - Null allows all activities to be streamed
+ @SuppressLint("NullableCollection")
+ @NonNull
public Builder setAllowedActivities(@Nullable Set<ComponentName> allowedActivities) {
if (mBlockedActivities != null && allowedActivities != null) {
throw new IllegalArgumentException(
@@ -279,8 +285,10 @@
*
* @param blockedActivities A set of {@link ComponentName} to be blocked launching from
* virtual device.
- * @hide // TODO(b/194949534): Unhide this API
*/
+ // Allowing null to enforce that at most one of allowed / blocked activities can be non-null
+ @SuppressLint("NullableCollection")
+ @NonNull
public Builder setBlockedActivities(@Nullable Set<ComponentName> blockedActivities) {
if (mAllowedActivities != null && blockedActivities != null) {
throw new IllegalArgumentException(
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4b4e008..52681630 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -37,6 +37,7 @@
import android.annotation.UiContext;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.BroadcastOptions;
import android.app.GameManager;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
@@ -2260,6 +2261,27 @@
}
/**
+ * Version of {@link #sendBroadcastMultiplePermissions(Intent, String[])} that allows you to
+ * specify the {@link android.app.BroadcastOptions}.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermissions Array of names of permissions that a receiver must hold
+ * in order to receive your broadcast.
+ * If empty, no permissions are required.
+ * @param options Additional sending options, generated from a
+ * {@link android.app.BroadcastOptions}.
+ * @see #sendBroadcastMultiplePermissions(Intent, String[])
+ * @see android.app.BroadcastOptions
+ * @hide
+ */
+ @SystemApi
+ public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+ @NonNull String[] receiverPermissions, @Nullable BroadcastOptions options) {
+ sendBroadcastMultiplePermissions(intent, receiverPermissions, options.toBundle());
+ }
+
+ /**
* Broadcast the given intent to all interested BroadcastReceivers, allowing
* an array of required permissions to be enforced. This call is asynchronous; it returns
* immediately, and you will continue executing while the receivers are run. No results are
@@ -4987,10 +5009,8 @@
* @hide
* @see #getSystemService(String)
*/
- // TODO(216507592): Change cloudsearch_service to cloudsearch.
@SystemApi
- @SuppressLint("ServiceName")
- public static final String CLOUDSEARCH_SERVICE = "cloudsearch_service";
+ public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
/**
* Use with {@link #getSystemService(String)} to access the
@@ -5011,6 +5031,20 @@
public static final String SOUND_TRIGGER_MIDDLEWARE_SERVICE = "soundtrigger_middleware";
/**
+ * Used for getting the wallpaper effects generation service.
+ *
+ * <p><b>NOTE: </b> this service is optional; callers of
+ * {@code Context.getSystemServiceName(WALLPAPER_EFFECTS_GENERATION_SERVICE)} should check for
+ * {@code null}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String WALLPAPER_EFFECTS_GENERATION_SERVICE =
+ "wallpaper_effects_generation";
+
+ /**
* Used to access {@link MusicRecognitionManagerService}.
*
* @hide
@@ -5638,6 +5672,15 @@
public static final String OVERLAY_SERVICE = "overlay";
/**
+ * Use with {@link #getSystemService(String)} to manage resources.
+ *
+ * @see #getSystemService(String)
+ * @see com.android.server.resources.ResourcesManagerService
+ * @hide
+ */
+ public static final String RESOURCES_SERVICE = "resources";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a
* {android.os.IIdmap2} for managing idmap files (used by overlay
* packages).
@@ -6677,21 +6720,27 @@
@NonNull Configuration overrideConfiguration);
/**
- * Returns a new <code>Context</code> object from the current context but with resources
- * adjusted to match the metrics of <code>display</code>. Each call to this method
+ * Returns a new {@code Context} object from the current context but with resources
+ * adjusted to match the metrics of {@code display}. Each call to this method
* returns a new instance of a context object. Context objects are not shared; however,
* common state (such as the {@link ClassLoader} and other resources for the same
- * configuration) can be shared, so the <code>Context</code> itself is lightweight.
+ * configuration) can be shared, so the {@code Context} itself is lightweight.
+ *
+ * <p><b>Note:</b>
+ * This {@code Context} is <b>not</b> expected to be updated with new configuration if the
+ * underlying display configuration changes and the cached {@code Resources} it returns
+ * could be stale. It is suggested to use
+ * {@link android.hardware.display.DisplayManager.DisplayListener} to listen for
+ * changes and re-create an instance if necessary. </p>
* <p>
+ * This {@code Context} is <b>not</b> a UI context, do not use it to access UI components
+ * or obtain a {@link WindowManager} instance.
+ * </p><p>
* To obtain an instance of {@link WindowManager} configured to show windows on the given
* display, call {@link #createWindowContext(int, Bundle)} on the returned display context,
* then call {@link #getSystemService(String)} or {@link #getSystemService(Class)} on the
* returned window context.
- * <p>
- * <b>Note:</b> The context returned by <code>createDisplayContext(Display)</code> is not a UI
- * context. Do not access UI components or obtain a {@link WindowManager} from the context
- * created by <code>createDisplayContext(Display)</code>.
- *
+ * </p>
* @param display The display to which the current context's resources are adjusted.
*
* @return A context for the display.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index fb186fd..3e527f8 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3986,7 +3986,7 @@
* {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
* @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @SystemApi
public static final String ACTION_USER_SWITCHED =
"android.intent.action.USER_SWITCHED";
@@ -6196,6 +6196,8 @@
*
* @hide
*/
+ @SystemApi
+ @SuppressLint("ActionValue")
public static final String EXTRA_USER_HANDLE =
"android.intent.extra.user_handle";
@@ -7059,6 +7061,7 @@
*
* @hide
*/
+ @SystemApi
public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000;
/**
* If set, the broadcast will never go to manifest receivers in background (cached
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 1e88758..94f0561 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -282,7 +282,8 @@
final boolean isManagedProfile =
mUserManager.isManagedProfile(userHandle.getIdentifier());
if (isManagedProfile) {
- return mResources.getDrawable(R.drawable.ic_corp_badge, null);
+ return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
+ userHandle, /* density= */ 0);
} else {
return UserIcons.getDefaultUserIcon(
mResources, UserHandle.USER_SYSTEM, true /* light */);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 1c82b38..30aed8b 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -653,6 +653,7 @@
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
String getPermissionControllerPackageName();
+ String getSupplementalProcessPackageName();
ParceledListSlice getInstantApps(int userId);
byte[] getInstantAppCookie(String packageName, int userId);
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index 9735f81..410e106 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -21,7 +21,7 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
/**
* Basic information about a package as specified in its manifest.
@@ -80,10 +80,10 @@
/**
* Specifies the recommended install location. Can be one of
- * {@link PackageHelper#RECOMMEND_INSTALL_INTERNAL} to install on internal storage,
- * {@link PackageHelper#RECOMMEND_INSTALL_EXTERNAL} to install on external media,
- * {@link PackageHelper#RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors,
- * or {@link PackageHelper#RECOMMEND_FAILED_INVALID_APK} for parse errors.
+ * {@link InstallLocationUtils#RECOMMEND_INSTALL_INTERNAL} to install on internal storage,
+ * {@link InstallLocationUtils#RECOMMEND_INSTALL_EXTERNAL} to install on external media,
+ * {@link InstallLocationUtils#RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors,
+ * or {@link InstallLocationUtils#RECOMMEND_FAILED_INVALID_APK} for parse errors.
*/
public int recommendedInstallLocation;
public int installLocation;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e5c31d7..aa64700 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5691,6 +5691,20 @@
}
/**
+ * Returns the package name of the component implementing supplemental process service.
+ *
+ * @return the package name of the component implementing supplemental process service
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public String getSupplementalProcessPackageName() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Add a new dynamic permission to the system. For this to work, your
* package must have defined a permission tree through the
* {@link android.R.styleable#AndroidManifestPermissionTree
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index 43a4b17..4c0e2e6 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -46,7 +46,7 @@
TYPE_BUILTIN,
TYPE_DYNAMIC,
TYPE_STATIC,
- TYPE_SDK,
+ TYPE_SDK_PACKAGE,
})
@Retention(RetentionPolicy.SOURCE)
@interface Type{}
@@ -68,15 +68,21 @@
* Shared library type: this library is <strong>not</strong> backwards
* -compatible, can be updated and updates can be uninstalled. Clients
* link against a specific version of the library.
+ *
+ * Static shared libraries simulate static linking while allowing for
+ * multiple clients to reuse the same instance of the library.
*/
public static final int TYPE_STATIC = 2;
/**
- * SDK library type: this library is <strong>not</strong> backwards
- * -compatible, can be updated and updates can be uninstalled. Clients
- * depend on a specific version of the library.
+ * SDK package shared library type: this library is <strong>not</strong>
+ * compatible between versions, can be updated and updates can be
+ * uninstalled. Clients depend on a specific version of the library.
+ *
+ * SDK packages are not loaded automatically by the OS and rely
+ * e.g. on 3P libraries to make them available for the clients.
*/
- public static final int TYPE_SDK = 3;
+ public static final int TYPE_SDK_PACKAGE = 3;
/**
* Constant for referring to an undefined version.
@@ -301,7 +307,7 @@
* @hide
*/
public boolean isSdk() {
- return mType == TYPE_SDK;
+ return mType == TYPE_SDK_PACKAGE;
}
/**
@@ -367,7 +373,7 @@
case TYPE_STATIC: {
return "static";
}
- case TYPE_SDK: {
+ case TYPE_SDK_PACKAGE: {
return "sdk";
}
default: {
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index a503d14..dea0834 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -162,5 +162,68 @@
{
"name": "CtsInstallHostTestCases"
}
+ ],
+ "staged-platinum-postsubmit": [
+ {
+ "name": "CtsIncrementalInstallHostTestCases"
+ },
+ {
+ "name": "CtsInstallHostTestCases"
+ },
+ {
+ "name": "CtsStagedInstallHostTestCases"
+ },
+ {
+ "name": "CtsExtractNativeLibsHostTestCases"
+ },
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "com.android.cts.splitapp.SplitAppTest"
+ },
+ {
+ "include-filter": "android.appsecurity.cts.EphemeralTest"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.pm.PackageParserTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsRollbackManagerHostTestCases"
+ },
+ {
+ "name": "CtsOsHostTestCases",
+ "options": [
+ {
+ "include-filter": "com.android.server.pm.PackageParserTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsContentTestCases",
+ "options": [
+ {
+ "include-filter": "android.content.cts.IntentFilterTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsAppEnumerationTestCases"
+ },
+ {
+ "name": "PackageManagerServiceUnitTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.pm.test.verify.domain"
+ }
+ ]
+ }
]
}
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 6fd2d05..7a5ac8e 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -28,6 +28,7 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -438,6 +439,12 @@
}
}
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "class=" + getClass());
+ pw.println(prefix + "debugName=" + getDebugName());
+ pw.println(prefix + "assetPath=" + getAssetPath());
+ }
+
private static native long nativeLoad(@FormatType int format, @NonNull String path,
@PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
private static native long nativeLoadEmpty(@PropertyFlags int flags,
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index bfd9fd0..a05f5c9 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -43,6 +43,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintWriter;
import java.lang.ref.Reference;
import java.util.ArrayList;
import java.util.Arrays;
@@ -1531,6 +1532,15 @@
}
}
+ synchronized void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "class=" + getClass());
+ pw.println(prefix + "apkAssets=");
+ for (int i = 0; i < mApkAssets.length; i++) {
+ pw.println(prefix + i);
+ mApkAssets[i].dump(pw, prefix + " ");
+ }
+ }
+
// AssetManager setup native methods.
private static native long nativeCreate();
private static native void nativeDestroy(long ptr);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/content/res/IResourcesManager.aidl
similarity index 67%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/content/res/IResourcesManager.aidl
index 861a4ed..d137378 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/content/res/IResourcesManager.aidl
@@ -14,6 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package android.content.res;
-parcelable DeviceInfo;
+import android.os.RemoteCallback;
+
+/**
+ * Api for getting information about resources.
+ *
+ * {@hide}
+ */
+interface IResourcesManager {
+ boolean dumpResources(in String process,
+ in ParcelFileDescriptor fd,
+ in RemoteCallback finishCallback);
+}
\ No newline at end of file
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 5fd0d84..ebef053 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -53,6 +53,7 @@
import android.graphics.drawable.DrawableInflater;
import android.os.Build;
import android.os.Bundle;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -78,11 +79,15 @@
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
/**
* Class for accessing an application's resources. This sits on top of the
@@ -172,6 +177,11 @@
private int mBaseApkAssetsSize;
+ /** @hide */
+ private static Set<Resources> sResourcesHistory = Collections.synchronizedSet(
+ Collections.newSetFromMap(
+ new WeakHashMap<>()));
+
/**
* Returns the most appropriate default theme for the specified target SDK version.
* <ul>
@@ -318,6 +328,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Resources(@Nullable ClassLoader classLoader) {
mClassLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader;
+ sResourcesHistory.add(this);
}
/**
@@ -2649,4 +2660,29 @@
}
}
}
+
+ /** @hide */
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "class=" + getClass());
+ pw.println(prefix + "resourcesImpl");
+ mResourcesImpl.dump(pw, prefix + " ");
+ }
+
+ /** @hide */
+ public static void dumpHistory(PrintWriter pw, String prefix) {
+ pw.println(prefix + "history");
+ // Putting into a map keyed on the apk assets to deduplicate resources that are different
+ // objects but ultimately represent the same assets
+ Map<List<ApkAssets>, Resources> history = new ArrayMap<>();
+ for (Resources r : sResourcesHistory) {
+ history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r);
+ }
+ int i = 0;
+ for (Resources r : history.values()) {
+ if (r != null) {
+ pw.println(prefix + i++);
+ r.dump(pw, prefix + " ");
+ }
+ }
+ }
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 4d850b0c..ff07291 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -61,6 +61,7 @@
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Locale;
@@ -1271,6 +1272,12 @@
NativeAllocationRegistry.createMalloced(ResourcesImpl.class.getClassLoader(),
AssetManager.getThemeFreeFunction());
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "class=" + getClass());
+ pw.println(prefix + "assets");
+ mAssets.dump(pw, prefix + " ");
+ }
+
public class ThemeImpl {
/**
* Unique key for the series of styles applied to this theme.
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index f13c795..52bba14 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -131,11 +131,16 @@
*
* @param name The name of the cursor window, or null if none.
* @param windowSizeBytes Size of cursor window in bytes.
+ * @throws IllegalArgumentException if {@code windowSizeBytes} is less than 0
+ * @throws AssertionError if created window pointer is 0
* <p><strong>Note:</strong> Memory is dynamically allocated as data rows are added to the
* window. Depending on the amount of data stored, the actual amount of memory allocated can be
* lower than specified size, but cannot exceed it.
*/
public CursorWindow(String name, @BytesLong long windowSizeBytes) {
+ if (windowSizeBytes < 0) {
+ throw new IllegalArgumentException("Window size cannot be less than 0");
+ }
mStartPos = 0;
mName = name != null && name.length() != 0 ? name : "<unnamed>";
mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
diff --git a/core/java/android/database/CursorWindowAllocationException.java b/core/java/android/database/CursorWindowAllocationException.java
index 2e3227d..5315c8b 100644
--- a/core/java/android/database/CursorWindowAllocationException.java
+++ b/core/java/android/database/CursorWindowAllocationException.java
@@ -16,14 +16,14 @@
package android.database;
+import android.annotation.NonNull;
+
/**
* This exception is thrown when a CursorWindow couldn't be allocated,
* most probably due to memory not being available.
- *
- * @hide
*/
public class CursorWindowAllocationException extends RuntimeException {
- public CursorWindowAllocationException(String description) {
+ public CursorWindowAllocationException(@NonNull String description) {
super(description);
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
similarity index 61%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/hardware/biometrics/IBiometricContextListener.aidl
index 861a4ed..55cab52 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
@@ -13,7 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.hardware.biometrics;
-package com.android.systemui.shared.mediattt;
-
-parcelable DeviceInfo;
+/**
+ * A secondary communication channel from AuthController back to BiometricService for
+ * events that are not associated with an autentication session. See
+ * {@link IBiometricSysuiReceiver} for events associated with a session.
+ *
+ * @hide
+ */
+oneway interface IBiometricContextListener {
+ void onDozeChanged(boolean isDozing);
+}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index c12e819..d6d3a97 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -25,6 +25,7 @@
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 @@
(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 @@
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/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index b06d076..30aa4db 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -79,9 +79,9 @@
* <ul>
* <li>The system deems the request can no longer be honored, for example if the requested
* state becomes unsupported.
- * <li>A call to {@link #cancelRequest(DeviceStateRequest)}.
+ * <li>A call to {@link #cancelStateRequest}.
* <li>Another processes submits a request succeeding this request in which case the request
- * will be suspended until the interrupting request is canceled.
+ * will be canceled.
* </ul>
* However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}.
*
@@ -100,19 +100,18 @@
}
/**
- * Cancels a {@link DeviceStateRequest request} previously submitted with a call to
+ * Cancels the active {@link DeviceStateRequest} previously submitted with a call to
* {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
* <p>
- * This method is noop if the {@code request} has not been submitted with a call to
- * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
+ * This method is noop if there is no request currently active.
*
* @throws SecurityException if the caller is neither the current top-focused activity nor if
* the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held.
*/
@RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
conditional = true)
- public void cancelRequest(@NonNull DeviceStateRequest request) {
- mGlobal.cancelRequest(request);
+ public void cancelStateRequest() {
+ mGlobal.cancelStateRequest();
}
/**
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index 85e70b0..aba538f 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -151,20 +151,14 @@
* Cancels a {@link DeviceStateRequest request} previously submitted with a call to
* {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
*
- * @see DeviceStateManager#cancelRequest(DeviceStateRequest)
+ * @see DeviceStateManager#cancelStateRequest
*/
- public void cancelRequest(@NonNull DeviceStateRequest request) {
+ public void cancelStateRequest() {
synchronized (mLock) {
registerCallbackIfNeededLocked();
- final IBinder token = findRequestTokenLocked(request);
- if (token == null) {
- // This request has not been submitted.
- return;
- }
-
try {
- mDeviceStateManager.cancelRequest(token);
+ mDeviceStateManager.cancelStateRequest();
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -299,20 +293,6 @@
/**
* Handles a call from the server that a request for the supplied {@code token} has become
- * suspended.
- */
- private void handleRequestSuspended(IBinder token) {
- DeviceStateRequestWrapper request;
- synchronized (mLock) {
- request = mRequests.get(token);
- }
- if (request != null) {
- request.notifyRequestSuspended();
- }
- }
-
- /**
- * Handles a call from the server that a request for the supplied {@code token} has become
* canceled.
*/
private void handleRequestCanceled(IBinder token) {
@@ -337,11 +317,6 @@
}
@Override
- public void onRequestSuspended(IBinder token) {
- handleRequestSuspended(token);
- }
-
- @Override
public void onRequestCanceled(IBinder token) {
handleRequestCanceled(token);
}
@@ -395,14 +370,6 @@
mExecutor.execute(() -> mCallback.onRequestActivated(mRequest));
}
- void notifyRequestSuspended() {
- if (mCallback == null) {
- return;
- }
-
- mExecutor.execute(() -> mCallback.onRequestSuspended(mRequest));
- }
-
void notifyRequestCanceled() {
if (mCallback == null) {
return;
diff --git a/core/java/android/hardware/devicestate/DeviceStateRequest.java b/core/java/android/hardware/devicestate/DeviceStateRequest.java
index df488d2..893d765 100644
--- a/core/java/android/hardware/devicestate/DeviceStateRequest.java
+++ b/core/java/android/hardware/devicestate/DeviceStateRequest.java
@@ -32,8 +32,7 @@
* DeviceStateRequest.Callback)}.
* <p>
* By default, the request is kept active until a call to
- * {@link DeviceStateManager#cancelRequest(DeviceStateRequest)} or until one of the following
- * occurs:
+ * {@link DeviceStateManager#cancelStateRequest} or until one of the following occurs:
* <ul>
* <li>Another processes submits a request succeeding this request in which case the request
* will be suspended until the interrupting request is canceled.
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index 14ed03d..e450e42 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -41,8 +41,9 @@
* previously registered with {@link #registerCallback(IDeviceStateManagerCallback)} before a
* call to this method.
*
- * @param token the request token previously registered with
- * {@link #requestState(IBinder, int, int)}
+ * @param token the request token provided
+ * @param state the state of device the request is asking for
+ * @param flags any flags that correspond to the request
*
* @throws IllegalStateException if a callback has not yet been registered for the calling
* process.
@@ -52,14 +53,11 @@
void requestState(IBinder token, int state, int flags);
/**
- * Cancels a request previously submitted with a call to
+ * Cancels the active request previously submitted with a call to
* {@link #requestState(IBinder, int, int)}.
*
- * @param token the request token previously registered with
- * {@link #requestState(IBinder, int, int)}
- *
- * @throws IllegalStateException if the supplied {@code token} has not been previously
- * requested.
+ * @throws IllegalStateException if a callback has not yet been registered for the calling
+ * process.
*/
- void cancelRequest(IBinder token);
+ void cancelStateRequest();
}
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
index efb9888..348690f 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
@@ -41,16 +41,6 @@
oneway void onRequestActive(IBinder token);
/**
- * Called to notify the callback that a request has become suspended. Guaranteed to be called
- * before a subsequent call to {@link #onDeviceStateInfoChanged(DeviceStateInfo)} if the request
- * becoming suspended resulted in a change of device state info.
- *
- * @param token the request token previously registered with
- * {@link IDeviceStateManager#requestState(IBinder, int, int)}
- */
- oneway void onRequestSuspended(IBinder token);
-
- /**
* Called to notify the callback that a request has become canceled. No further callbacks will
* be triggered for this request. Guaranteed to be called before a subsequent call to
* {@link #onDeviceStateInfoChanged(DeviceStateInfo)} if the request becoming canceled resulted
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 1a7a63ae..af8ec27 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -916,6 +916,17 @@
}
/**
+ * 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 35663af..b3af52b 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -168,6 +168,7 @@
// 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 e7d76f6..bb34646 100644
--- a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
+++ b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
@@ -142,7 +142,7 @@
* @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/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java
index cee6a5b..b96e4f8 100644
--- a/core/java/android/hardware/hdmi/HdmiClient.java
+++ b/core/java/android/hardware/hdmi/HdmiClient.java
@@ -21,6 +21,8 @@
public abstract class HdmiClient {
private static final String TAG = "HdmiClient";
+ private static final int UNKNOWN_VENDOR_ID = 0xFFFFFF;
+
/* package */ final IHdmiControlService mService;
private IHdmiVendorCommandListener mIHdmiVendorCommandListener;
@@ -156,11 +158,25 @@
}
/**
- * Sets a listener used to receive incoming vendor-specific command.
+ * Sets a listener used to receive incoming vendor-specific command. This listener will only
+ * receive {@code <Vendor Command>} but will not receive any {@code <Vendor Command with ID>}
+ * messages.
*
* @param listener listener object
*/
public void setVendorCommandListener(@NonNull VendorCommandListener listener) {
+ // Set the vendor ID to INVALID_VENDOR_ID.
+ setVendorCommandListener(listener, UNKNOWN_VENDOR_ID);
+ }
+
+ /**
+ * Sets a listener used to receive incoming vendor-specific command.
+ *
+ * @param listener listener object
+ * @param vendorId The listener is interested in {@code <Vendor Command with ID>} received with
+ * this vendorId and all {@code <Vendor Command>} messages.
+ */
+ public void setVendorCommandListener(@NonNull VendorCommandListener listener, int vendorId) {
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null");
}
@@ -169,7 +185,7 @@
}
try {
IHdmiVendorCommandListener wrappedListener = getListenerWrapper(listener);
- mService.addVendorCommandListener(wrappedListener, getDeviceType());
+ mService.addVendorCommandListener(wrappedListener, vendorId);
mIHdmiVendorCommandListener = wrappedListener;
} catch (RemoteException e) {
Log.e(TAG, "failed to set vendor command listener: ", e);
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 5874385..9235ba1 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -105,6 +105,12 @@
"android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1";
/**
+ * Used as an extra field in the Set Menu Language intent. Contains the requested locale.
+ * @hide
+ */
+ public static final String EXTRA_LOCALE = "android.hardware.hdmi.extra.LOCALE";
+
+ /**
* Volume value for mute state.
*/
public static final int AVR_VOLUME_MUTED = 101;
@@ -558,6 +564,32 @@
@Retention(RetentionPolicy.SOURCE)
public @interface TvSendStandbyOnSleep {}
+ // -- Whether a playback device should act on an incoming {@code <Set Menu Language>} message.
+ /**
+ * Confirmation dialog should be shown upon receiving the CEC message.
+ *
+ * @see HdmiControlManager#CEC_SETTING_NAME_SET_MENU_LANGUAGE
+ * @hide
+ */
+ public static final int SET_MENU_LANGUAGE_ENABLED = 1;
+ /**
+ * The message should be ignored.
+ *
+ * @see HdmiControlManager#CEC_SETTING_NAME_SET_MENU_LANGUAGE
+ * @hide
+ */
+ public static final int SET_MENU_LANGUAGE_DISABLED = 0;
+ /**
+ * @see HdmiControlManager#CEC_SETTING_NAME_SET_MENU_LANGUAGE
+ * @hide
+ */
+ @IntDef(prefix = { "SET_MENU_LANGUAGE_" }, value = {
+ SET_MENU_LANGUAGE_ENABLED,
+ SET_MENU_LANGUAGE_DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SetMenuLanguage {}
+
// -- The RC profile of a TV panel.
/**
* RC profile none.
@@ -812,6 +844,13 @@
public static final String CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP =
"tv_send_standby_on_sleep";
/**
+ * Name of a setting deciding whether {@code <Set Menu Language>} message should be
+ * handled by the framework or ignored.
+ *
+ * @hide
+ */
+ public static final String CEC_SETTING_NAME_SET_MENU_LANGUAGE = "set_menu_language";
+ /**
* Name of a setting representing the RC profile of a TV panel.
*
* @hide
@@ -977,6 +1016,7 @@
CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+ CEC_SETTING_NAME_SET_MENU_LANGUAGE,
CEC_SETTING_NAME_RC_PROFILE_TV,
CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
diff --git a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
index 16adee9..818554d 100644
--- a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
+++ b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
@@ -221,8 +221,8 @@
}
@Override
- public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
- HdmiControlServiceWrapper.this.addVendorCommandListener(listener, deviceType);
+ public void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) {
+ HdmiControlServiceWrapper.this.addVendorCommandListener(listener, vendorId);
}
@Override
@@ -481,7 +481,7 @@
boolean hasVendorId) {}
/** @hide */
- public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {}
+ public void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) {}
/** @hide */
public void sendStandby(int deviceType, int deviceId) {}
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 6613397..35dd9ed 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -76,7 +76,7 @@
void askRemoteDeviceToBecomeActiveSource(int physicalAddress);
void sendVendorCommand(int deviceType, int targetAddress, in byte[] params,
boolean hasVendorId);
- void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType);
+ void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId);
void sendStandby(int deviceType, int deviceId);
void setHdmiRecordListener(IHdmiRecordListener callback);
void startOneTouchRecord(int recorderAddress, in byte[] recordSource);
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 0304815..e1ffd4a 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -112,7 +112,7 @@
oneway void requestPointerCapture(IBinder inputChannelToken, boolean enabled);
/** Create an input monitor for gestures. */
- InputMonitor monitorGestureInput(String name, int displayId);
+ InputMonitor monitorGestureInput(IBinder token, String name, int displayId);
// Add a runtime association between the input port and the display port. This overrides any
// static associations.
@@ -122,9 +122,9 @@
void removePortAssociation(in String inputPort);
// Add a runtime association between the input device and display.
- void addUniqueIdAssociation(in String inputDeviceName, in String displayUniqueId);
+ void addUniqueIdAssociation(in String inputPort, in String displayUniqueId);
// Remove the runtime association between the input device and display.
- void removeUniqueIdAssociation(in String inputDeviceName);
+ void removeUniqueIdAssociation(in String inputPort);
InputSensorInfo[] getSensorList(int deviceId);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index cbc8373..2fd79cf 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -35,6 +35,7 @@
import android.hardware.lights.LightState;
import android.hardware.lights.LightsManager;
import android.hardware.lights.LightsRequest;
+import android.os.Binder;
import android.os.BlockUntrustedTouchesMode;
import android.os.Build;
import android.os.CombinedVibration;
@@ -1211,7 +1212,7 @@
*/
public InputMonitor monitorGestureInput(String name, int displayId) {
try {
- return mIm.monitorGestureInput(name, displayId);
+ return mIm.monitorGestureInput(new Binder(), name, displayId);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -1359,19 +1360,18 @@
}
/**
- * Add a runtime association between the input device name and display, by unique id. Input
- * device names are expected to be unique.
- * @param inputDeviceName The name of the input device.
+ * Add a runtime association between the input port and display, by unique id. Input ports are
+ * expected to be unique.
+ * @param inputPort The port of the input device.
* @param displayUniqueId The unique id of the associated display.
* <p>
* Requires {@link android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
- public void addUniqueIdAssociation(@NonNull String inputDeviceName,
- @NonNull String displayUniqueId) {
+ public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
try {
- mIm.addUniqueIdAssociation(inputDeviceName, displayUniqueId);
+ mIm.addUniqueIdAssociation(inputPort, displayUniqueId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1379,15 +1379,15 @@
/**
* Removes a runtime association between the input device and display.
- * @param inputDeviceName The name of the input device.
+ * @param inputPort The port of the input device.
* <p>
* Requires {@link android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
- public void removeUniqueIdAssociation(@NonNull String inputDeviceName) {
+ public void removeUniqueIdAssociation(@NonNull String inputPort) {
try {
- mIm.removeUniqueIdAssociation(inputDeviceName);
+ mIm.removeUniqueIdAssociation(inputPort);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 60f5135..7ff74c6 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -1341,7 +1341,6 @@
*
* @hide
*/
- @SystemApi
@RequiresPermission(Manifest.permission.MANAGE_USB)
boolean resetUsbPort(@NonNull UsbPort port, int operationId,
IUsbOperationInternal callback) {
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 41642e7..af57f79 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -70,6 +70,7 @@
private static final int DO_SET_INPUT_CONTEXT = 20;
private static final int DO_UNSET_INPUT_CONTEXT = 30;
private static final int DO_START_INPUT = 32;
+ private static final int DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED = 35;
private static final int DO_CREATE_SESSION = 40;
private static final int DO_SET_SESSION_ENABLED = 45;
private static final int DO_SHOW_SOFT_INPUT = 60;
@@ -175,7 +176,7 @@
try {
inputMethod.initializeInternal((IBinder) args.arg1,
(IInputMethodPrivilegedOperations) args.arg2, msg.arg1,
- (boolean) args.arg3);
+ (boolean) args.arg3, msg.arg2 != 0);
} finally {
args.recycle();
}
@@ -195,14 +196,22 @@
final EditorInfo info = (EditorInfo) args.arg3;
final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
final boolean restarting = args.argi5 == 1;
+ final boolean shouldShowImeSwitcherWhenImeIsShown = args.argi6 != 0;
final InputConnection ic = inputContext != null
? new RemoteInputConnection(mTarget, inputContext, cancellationGroup)
: null;
info.makeCompatible(mTargetSdkVersion);
- inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken);
+ inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken,
+ shouldShowImeSwitcherWhenImeIsShown);
args.recycle();
return;
}
+ case DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED: {
+ final boolean shouldShowImeSwitcherWhenImeIsShown = msg.arg1 != 0;
+ inputMethod.onShouldShowImeSwitcherWhenImeIsShownChanged(
+ shouldShowImeSwitcherWhenImeIsShown);
+ return;
+ }
case DO_CREATE_SESSION: {
SomeArgs args = (SomeArgs)msg.obj;
inputMethod.createSession(new InputMethodSessionCallbackWrapper(
@@ -291,10 +300,11 @@
@BinderThread
@Override
public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
- int configChanges, boolean stylusHwSupported) {
- mCaller.executeOrSendMessage(
- mCaller.obtainMessageIOOO(
- DO_INITIALIZE_INTERNAL, configChanges, token, privOps, stylusHwSupported));
+ int configChanges, boolean stylusHwSupported,
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_INITIALIZE_INTERNAL,
+ configChanges, shouldShowImeSwitcherWhenImeIsShown ? 1 : 0, token, privOps,
+ stylusHwSupported));
}
@BinderThread
@@ -334,13 +344,23 @@
@BinderThread
@Override
public void startInput(IBinder startInputToken, IInputContext inputContext,
- EditorInfo attribute, boolean restarting) {
+ EditorInfo attribute, boolean restarting, boolean shouldShowImeSwitcherWhenImeIsShown) {
if (mCancellationGroup == null) {
Log.e(TAG, "startInput must be called after bindInput.");
mCancellationGroup = new CancellationGroup();
}
mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken,
- inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, 0 /* unused */));
+ inputContext, attribute, mCancellationGroup, restarting ? 1 : 0,
+ shouldShowImeSwitcherWhenImeIsShown ? 1 : 0));
+ }
+
+ @BinderThread
+ @Override
+ public void onShouldShowImeSwitcherWhenImeIsShownChanged(
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageI(
+ DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED,
+ shouldShowImeSwitcherWhenImeIsShown ? 1 : 0));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 14f92fb..fc2fbc3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -346,7 +346,7 @@
*/
@AnyThread
public static boolean canImeRenderGesturalNavButtons() {
- return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, false);
+ return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, true);
}
/**
@@ -475,7 +475,7 @@
private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations();
@NonNull
- final NavigationBarController mNavigationBarController =
+ private final NavigationBarController mNavigationBarController =
new NavigationBarController(this);
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -658,7 +658,7 @@
@Override
public final void initializeInternal(@NonNull IBinder token,
IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
- boolean stylusHwSupported) {
+ boolean stylusHwSupported, boolean shouldShowImeSwitcherWhenImeIsShown) {
if (mDestroyed) {
Log.i(TAG, "The InputMethodService has already onDestroyed()."
+ "Ignore the initialization.");
@@ -671,6 +671,8 @@
if (stylusHwSupported) {
mInkWindow = new InkWindow(mWindow.getContext());
}
+ mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown(
+ shouldShowImeSwitcherWhenImeIsShown);
attachToken(token);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -780,9 +782,10 @@
@Override
public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
@NonNull EditorInfo editorInfo, boolean restarting,
- @NonNull IBinder startInputToken) {
+ @NonNull IBinder startInputToken, boolean shouldShowImeSwitcherWhenImeIsShown) {
mPrivOps.reportStartInputAsync(startInputToken);
-
+ mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown(
+ shouldShowImeSwitcherWhenImeIsShown);
if (restarting) {
restartInput(inputConnection, editorInfo);
} else {
@@ -796,6 +799,18 @@
*/
@MainThread
@Override
+ public void onShouldShowImeSwitcherWhenImeIsShownChanged(
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
+ mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown(
+ shouldShowImeSwitcherWhenImeIsShown);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @MainThread
+ @Override
public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
IBinder hideInputToken) {
mSystemCallingHideSoftInput = true;
@@ -1489,7 +1504,7 @@
Context.LAYOUT_INFLATER_SERVICE);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow");
mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
-
+ mNavigationBarController.onSoftInputWindowCreated(mWindow);
{
final Window window = mWindow.getWindow();
{
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 2484cf0..e5c22e4 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -19,6 +19,7 @@
import static android.content.Intent.ACTION_OVERLAY_CHANGED;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import android.animation.ValueAnimator;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,6 +29,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
@@ -44,6 +46,8 @@
import android.view.WindowInsets;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowManagerPolicyConstants;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import java.util.Objects;
@@ -62,6 +66,9 @@
@NonNull ViewTreeObserver.InternalInsetsInfo dest) {
}
+ default void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
+ }
+
default void onViewInitialized() {
}
@@ -71,7 +78,8 @@
default void onDestroy() {
}
- default void onSystemBarAppearanceChanged(@Appearance int appearance) {
+ default void setShouldShowImeSwitcherWhenImeIsShown(
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
}
default String toDebugString() {
@@ -94,6 +102,10 @@
mImpl.updateTouchableInsets(originalInsets, dest);
}
+ void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
+ mImpl.onSoftInputWindowCreated(softInputWindow);
+ }
+
void onViewInitialized() {
mImpl.onViewInitialized();
}
@@ -106,15 +118,21 @@
mImpl.onDestroy();
}
- void onSystemBarAppearanceChanged(@Appearance int appearance) {
- mImpl.onSystemBarAppearanceChanged(appearance);
+ void setShouldShowImeSwitcherWhenImeIsShown(boolean shouldShowImeSwitcherWhenImeIsShown) {
+ mImpl.setShouldShowImeSwitcherWhenImeIsShown(shouldShowImeSwitcherWhenImeIsShown);
}
String toDebugString() {
return mImpl.toDebugString();
}
- private static final class Impl implements Callback {
+ private static final class Impl implements Callback, Window.DecorCallback {
+ private static final int DEFAULT_COLOR_ADAPT_TRANSITION_TIME = 1700;
+
+ // Copied from com.android.systemui.animation.Interpolators#LEGACY_DECELERATE
+ private static final Interpolator LEGACY_DECELERATE =
+ new PathInterpolator(0f, 0f, 0.2f, 1f);
+
@NonNull
private final InputMethodService mService;
@@ -130,9 +148,19 @@
@Nullable
private BroadcastReceiver mSystemOverlayChangedReceiver;
+ private boolean mShouldShowImeSwitcherWhenImeIsShown;
+
@Appearance
private int mAppearance;
+ @FloatRange(from = 0.0f, to = 1.0f)
+ private float mDarkIntensity;
+
+ @Nullable
+ private ValueAnimator mTintAnimator;
+
+ private boolean mDrawLegacyNavigationBarBackground;
+
Impl(@NonNull InputMethodService inputMethodService) {
mService = inputMethodService;
}
@@ -190,7 +218,9 @@
// TODO(b/213337792): Support InputMethodService#setBackDisposition().
// TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary.
final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
- | StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+ | (mShouldShowImeSwitcherWhenImeIsShown
+ ? StatusBarManager.NAVIGATION_HINT_IME_SHOWN
+ : 0);
navigationBarView.setNavigationIconHints(hints);
}
} else {
@@ -199,9 +229,14 @@
mLastInsets = systemInsets;
}
- mNavigationBarFrame.setBackground(null);
+ if (mDrawLegacyNavigationBarBackground) {
+ mNavigationBarFrame.setBackgroundColor(Color.BLACK);
+ } else {
+ mNavigationBarFrame.setBackground(null);
+ }
- setIconTintInternal(calculateTargetDarkIntensity(mAppearance));
+ setIconTintInternal(calculateTargetDarkIntensity(mAppearance,
+ mDrawLegacyNavigationBarBackground));
}
private void uninstallNavigationBarFrameIfNecessary() {
@@ -335,6 +370,13 @@
}
@Override
+ public void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
+ final Window window = softInputWindow.getWindow();
+ mAppearance = window.getSystemBarAppearance();
+ window.setDecorCallback(this);
+ }
+
+ @Override
public void onViewInitialized() {
if (mDestroyed) {
return;
@@ -368,6 +410,10 @@
if (mDestroyed) {
return;
}
+ if (mTintAnimator != null) {
+ mTintAnimator.cancel();
+ mTintAnimator = null;
+ }
if (mSystemOverlayChangedReceiver != null) {
mService.unregisterReceiver(mSystemOverlayChangedReceiver);
mSystemOverlayChangedReceiver = null;
@@ -404,6 +450,31 @@
}
@Override
+ public void setShouldShowImeSwitcherWhenImeIsShown(
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
+ if (mDestroyed) {
+ return;
+ }
+ if (mShouldShowImeSwitcherWhenImeIsShown == shouldShowImeSwitcherWhenImeIsShown) {
+ return;
+ }
+ mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
+
+ if (mNavigationBarFrame == null) {
+ return;
+ }
+ final NavigationBarView navigationBarView =
+ mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance);
+ if (navigationBarView == null) {
+ return;
+ }
+ final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
+ | (shouldShowImeSwitcherWhenImeIsShown
+ ? StatusBarManager.NAVIGATION_HINT_IME_SHOWN : 0);
+ navigationBarView.setNavigationIconHints(hints);
+ }
+
+ @Override
public void onSystemBarAppearanceChanged(@Appearance int appearance) {
if (mDestroyed) {
return;
@@ -415,11 +486,26 @@
return;
}
- final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance);
- setIconTintInternal(targetDarkIntensity);
+ final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance,
+ mDrawLegacyNavigationBarBackground);
+
+ if (mTintAnimator != null) {
+ mTintAnimator.cancel();
+ }
+ mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity);
+ mTintAnimator.addUpdateListener(
+ animation -> setIconTintInternal((Float) animation.getAnimatedValue()));
+ mTintAnimator.setDuration(DEFAULT_COLOR_ADAPT_TRANSITION_TIME);
+ mTintAnimator.setStartDelay(0);
+ mTintAnimator.setInterpolator(LEGACY_DECELERATE);
+ mTintAnimator.start();
}
private void setIconTintInternal(float darkIntensity) {
+ mDarkIntensity = darkIntensity;
+ if (mNavigationBarFrame == null) {
+ return;
+ }
final NavigationBarView navigationBarView =
mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance);
if (navigationBarView == null) {
@@ -429,16 +515,41 @@
}
@FloatRange(from = 0.0f, to = 1.0f)
- private static float calculateTargetDarkIntensity(@Appearance int appearance) {
- final boolean lightNavBar = (appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0;
+ private static float calculateTargetDarkIntensity(@Appearance int appearance,
+ boolean drawLegacyNavigationBarBackground) {
+ final boolean lightNavBar = !drawLegacyNavigationBarBackground
+ && (appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0;
return lightNavBar ? 1.0f : 0.0f;
}
@Override
+ public boolean onDrawLegacyNavigationBarBackgroundChanged(
+ boolean drawLegacyNavigationBarBackground) {
+ if (mDestroyed) {
+ return false;
+ }
+
+ if (drawLegacyNavigationBarBackground != mDrawLegacyNavigationBarBackground) {
+ mDrawLegacyNavigationBarBackground = drawLegacyNavigationBarBackground;
+ if (mDrawLegacyNavigationBarBackground) {
+ mNavigationBarFrame.setBackgroundColor(Color.BLACK);
+ } else {
+ mNavigationBarFrame.setBackground(null);
+ }
+ scheduleRelayout();
+ onSystemBarAppearanceChanged(mAppearance);
+ }
+ return drawLegacyNavigationBarBackground;
+ }
+
+ @Override
public String toDebugString() {
return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons
+ " mNavigationBarFrame=" + mNavigationBarFrame
+ + " mShouldShowImeSwitcherWhenImeIsShown" + mShouldShowImeSwitcherWhenImeIsShown
+ " mAppearance=0x" + Integer.toHexString(mAppearance)
+ + " mDarkIntensity=" + mDarkIntensity
+ + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground
+ "}";
}
}
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 0893d2a..5704dac 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -31,7 +31,6 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.WindowInsetsController;
import android.view.WindowManager;
import java.lang.annotation.Retention;
@@ -264,11 +263,6 @@
}
}
- @Override
- public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
- mService.mNavigationBarController.onSystemBarAppearanceChanged(appearance);
- }
-
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
mBounds.dumpDebug(proto, BOUNDS);
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index c936bfa..9122adf 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -523,9 +523,11 @@
*
* @param subId the subscriber to get the subscription plans for.
* @param callingPackage the name of the package making the call.
+ * @return the active {@link SubscriptionPlan}s for the given subscription id, or
+ * {@code null} if not found.
* @hide
*/
- @NonNull
+ @Nullable
public SubscriptionPlan[] getSubscriptionPlans(int subId, @NonNull String callingPackage) {
try {
return mService.getSubscriptionPlans(subId, callingPackage);
@@ -538,7 +540,7 @@
* Get subscription plan for the given networkTemplate.
*
* @param template the networkTemplate to get the subscription plan for.
- * @return the active {@link SubscriptionPlan} for the given template, or
+ * @return the active {@link SubscriptionPlan}s for the given template, or
* {@code null} if not found.
* @hide
*/
diff --git a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
index 24c22a9..9772bde 100644
--- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
+++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
@@ -16,9 +16,7 @@
package android.net.netstats;
-import static android.app.usage.NetworkStatsManager.PREFIX_UID;
-import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG;
-import static android.app.usage.NetworkStatsManager.PREFIX_XT;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
@@ -28,6 +26,7 @@
import static android.net.NetworkStats.TAG_NONE;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.net.NetworkIdentity;
import android.net.NetworkStatsCollection;
import android.net.NetworkStatsHistory;
@@ -54,12 +53,27 @@
import java.util.Set;
/**
- * Helper class to read old version of persistent network statistics, the implementation is
- * intended to be modified by OEM partners to accommodate their custom changes.
+ * Helper class to read old version of persistent network statistics.
+ *
+ * The implementation is intended to be modified by OEM partners to
+ * accommodate their custom changes.
+ *
* @hide
*/
-// @SystemApi(client = MODULE_LIBRARIES)
+@SystemApi(client = MODULE_LIBRARIES)
public class NetworkStatsDataMigrationUtils {
+ /**
+ * Prefix of the files which are used to store per network interface statistics.
+ */
+ public static final String PREFIX_XT = "xt";
+ /**
+ * Prefix of the files which are used to store per uid statistics.
+ */
+ public static final String PREFIX_UID = "uid";
+ /**
+ * Prefix of the files which are used to store per uid tagged traffic statistics.
+ */
+ public static final String PREFIX_UID_TAG = "uid_tag";
private static final HashMap<String, String> sPrefixLegacyFileNameMap =
new HashMap<String, String>() {{
@@ -146,17 +160,51 @@
}
/**
- * Read legacy persisted network stats from disk. This function provides a default
- * implementation to read persisted network stats from two different locations.
- * And this is intended to be modified by OEM to read from custom file format or
- * locations if necessary.
+ * Read legacy persisted network stats from disk.
+ *
+ * This function provides the implementation to read legacy network stats
+ * from disk. It is used for migration of legacy network stats into the
+ * stats provided by the Connectivity module.
+ * This function needs to know about the previous format(s) of the network
+ * stats data that might be stored on this device so it can be read and
+ * conserved upon upgrade to Android 13 or above.
+ *
+ * This function will be called multiple times sequentially, all on the
+ * same thread, and will not be called multiple times concurrently. This
+ * function is expected to do a substantial amount of disk access, and
+ * doesn't need to return particularly fast, but the first boot after
+ * an upgrade to Android 13+ will be held until migration is done. As
+ * migration is only necessary once, after the first boot following the
+ * upgrade, this delay is not incurred.
+ *
+ * If this function fails in any way, it should throw an exception. If this
+ * happens, the system can't know about the data that was stored in the
+ * legacy files, but it will still count data usage happening on this
+ * session. On the next boot, the system will try migration again, and
+ * merge the returned data with the data used with the previous session.
+ * The system will only try the migration up to three (3) times. The remaining
+ * count is stored in the netstats_import_legacy_file_needed device config. The
+ * legacy data is never deleted by the mainline module to avoid any possible
+ * data loss.
+ *
+ * It is possible to set the netstats_import_legacy_file_needed device config
+ * to any positive integer to force the module to perform the migration. This
+ * can be achieved by calling the following command before rebooting :
+ * adb shell device_config put connectivity netstats_import_legacy_file_needed 1
+ *
+ * The AOSP implementation provides code to read persisted network stats as
+ * they were written by AOSP prior to Android 13.
+ * OEMs who have used the AOSP implementation of persisting network stats
+ * to disk don't need to change anything.
+ * OEM that had modifications to this format should modify this function
+ * to read from their custom file format or locations if necessary.
*
* @param prefix Type of data which is being read by the service.
* @param bucketDuration Duration of the buckets of the object, in milliseconds.
* @return {@link NetworkStatsCollection} instance.
*/
@NonNull
- public static NetworkStatsCollection readPlatformCollectionLocked(
+ public static NetworkStatsCollection readPlatformCollection(
@NonNull String prefix, long bucketDuration) throws IOException {
final NetworkStatsCollection.Builder builder =
new NetworkStatsCollection.Builder(bucketDuration);
@@ -397,7 +445,7 @@
if (version >= IdentitySetVersion.VERSION_ADD_OEM_MANAGED_NETWORK) {
oemNetCapabilities = in.readInt();
} else {
- oemNetCapabilities = NetworkIdentity.OEM_NONE;
+ oemNetCapabilities = NetworkTemplate.OEM_MANAGED_NO;
}
// Legacy files might contain TYPE_MOBILE_* types which were deprecated in later
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 2d33817..07a5132 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -34,6 +34,7 @@
import android.service.batterystats.BatteryStatsServiceDumpHistoryProto;
import android.service.batterystats.BatteryStatsServiceDumpProto;
import android.telephony.CellSignalStrength;
+import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.text.format.DateFormat;
import android.util.ArrayMap;
@@ -2654,6 +2655,46 @@
*/
public abstract Timer getPhoneDataConnectionTimer(int dataType);
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_OTHER = 0;
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_LTE = 1;
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_NR = 2;
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_COUNT = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "RADIO_ACCESS_TECHNOLOGY_",
+ value = {RADIO_ACCESS_TECHNOLOGY_OTHER, RADIO_ACCESS_TECHNOLOGY_LTE,
+ RADIO_ACCESS_TECHNOLOGY_NR})
+ public @interface RadioAccessTechnology {
+ }
+
+ /** @hide */
+ public static final String[] RADIO_ACCESS_TECHNOLOGY_NAMES = {"Other", "LTE", "NR"};
+
+ /**
+ * Returns the time in microseconds that the mobile radio has been active on a
+ * given Radio Access Technology (RAT), at a given frequency (NR RAT only), for a given
+ * transmission power level.
+ *
+ * @param rat Radio Access Technology {@see RadioAccessTechnology}
+ * @param frequencyRange frequency range {@see ServiceState.FrequencyRange}, only needed for
+ * RADIO_ACCESS_TECHNOLOGY_NR. Use
+ * {@link ServiceState.FREQUENCY_RANGE_UNKNOWN} for other Radio Access
+ * Technologies.
+ * @param signalStrength the cellular signal strength. {@see CellSignalStrength#getLevel()}
+ * @param elapsedRealtimeMs current elapsed realtime
+ * @return time (in milliseconds) the mobile radio spent active in the specified state,
+ * while on battery.
+ * @hide
+ */
+ public abstract long getActiveRadioDurationMs(@RadioAccessTechnology int rat,
+ @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+ long elapsedRealtimeMs);
+
static final String[] WIFI_SUPPL_STATE_NAMES = {
"invalid", "disconn", "disabled", "inactive", "scanning",
"authenticating", "associating", "associated", "4-way-handshake",
@@ -3997,6 +4038,89 @@
}
}
+ private void printCellularPerRatBreakdown(PrintWriter pw, StringBuilder sb, String prefix,
+ long rawRealtimeMs) {
+ final String allFrequenciesHeader =
+ " All frequencies:\n";
+ final String[] nrFrequencyRangeDescription = new String[]{
+ " Unknown frequency:\n",
+ " Low frequency (less than 1GHz):\n",
+ " Middle frequency (1GHz to 3GHz):\n",
+ " High frequency (3GHz to 6GHz):\n",
+ " Mmwave frequency (greater than 6GHz):\n"};
+ final String signalStrengthHeader =
+ " Signal Strength Time:\n";
+ final String[] signalStrengthDescription = new String[]{
+ " unknown: ",
+ " poor: ",
+ " moderate: ",
+ " good: ",
+ " great: "};
+
+ final long totalActiveTimesMs = getMobileRadioActiveTime(rawRealtimeMs * 1000,
+ STATS_SINCE_CHARGED) / 1000;
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append("Active Cellular Radio Access Technology Breakdown:");
+ pw.println(sb);
+
+ boolean hasData = false;
+ final int numSignalStrength = CellSignalStrength.getNumSignalStrengthLevels();
+ for (int rat = RADIO_ACCESS_TECHNOLOGY_COUNT - 1; rat >= 0; rat--) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" ");
+ sb.append(RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+ sb.append(":\n");
+ sb.append(prefix);
+
+ final int numFreqLvl =
+ rat == RADIO_ACCESS_TECHNOLOGY_NR ? nrFrequencyRangeDescription.length : 1;
+ for (int freqLvl = numFreqLvl - 1; freqLvl >= 0; freqLvl--) {
+ final int freqDescriptionStart = sb.length();
+ boolean hasFreqData = false;
+ if (rat == RADIO_ACCESS_TECHNOLOGY_NR) {
+ sb.append(nrFrequencyRangeDescription[freqLvl]);
+ } else {
+ sb.append(allFrequenciesHeader);
+ }
+
+ sb.append(prefix);
+ sb.append(signalStrengthHeader);
+ for (int strength = 0; strength < numSignalStrength; strength++) {
+ final long timeMs = getActiveRadioDurationMs(rat, freqLvl, strength,
+ rawRealtimeMs);
+ if (timeMs <= 0) continue;
+ hasFreqData = true;
+ sb.append(prefix);
+ sb.append(signalStrengthDescription[strength]);
+ formatTimeMs(sb, timeMs);
+ sb.append("(");
+ sb.append(formatRatioLocked(timeMs, totalActiveTimesMs));
+ sb.append(")\n");
+ }
+
+ if (hasFreqData) {
+ hasData = true;
+ pw.print(sb);
+ sb.setLength(0);
+ sb.append(prefix);
+ } else {
+ // No useful data was printed, rewind sb to before the start of this frequency.
+ sb.setLength(freqDescriptionStart);
+ }
+ }
+ }
+
+ if (!hasData) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" (no activity)");
+ pw.println(sb);
+ }
+ }
+
/**
* Temporary for settings.
*/
@@ -5269,6 +5393,8 @@
printControllerActivity(pw, sb, prefix, CELLULAR_CONTROLLER_NAME,
getModemControllerActivity(), which);
+ printCellularPerRatBreakdown(pw, sb, prefix + " ", rawRealtimeMs);
+
pw.print(" Cellular data received: "); pw.println(formatBytesLocked(mobileRxTotalBytes));
pw.print(" Cellular data sent: "); pw.println(formatBytesLocked(mobileTxTotalBytes));
pw.print(" Cellular packets received: "); pw.println(mobileRxTotalPackets);
diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java
index 2a609b8..f16bbc6 100644
--- a/core/java/android/os/BatteryStatsManager.java
+++ b/core/java/android/os/BatteryStatsManager.java
@@ -24,6 +24,8 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
import android.content.Context;
import android.net.NetworkStack;
import android.os.connectivity.CellularBatteryStats;
@@ -515,6 +517,42 @@
}
/**
+ * Indicates that Bluetooth was toggled on.
+ *
+ * @param uid calling package uid
+ * @param reason why Bluetooth has been turned on
+ * @param packageName package responsible for this change
+ */
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ public void reportBluetoothOn(int uid, int reason, @NonNull String packageName) {
+ try {
+ mBatteryStats.noteBluetoothOn(uid, reason, packageName);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that Bluetooth was toggled off.
+ *
+ * @param uid calling package uid
+ * @param reason why Bluetooth has been turned on
+ * @param packageName package responsible for this change
+ */
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ public void reportBluetoothOff(int uid, int reason, @NonNull String packageName) {
+ try {
+ mBatteryStats.noteBluetoothOff(uid, reason, packageName);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Indicates that a new Bluetooth LE scan has started.
*
* @param ws worksource (to be used for battery blaming).
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 1b7c00c..6330661 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -526,12 +526,15 @@
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 @@
}
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 35b9ccc..9970641 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -31,6 +31,7 @@
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 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 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/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index aa4b83a..0c3514f 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -165,7 +165,7 @@
private boolean isAngleEnabledByGameMode(Context context, String packageName) {
try {
final boolean gameModeEnabledAngle =
- (mGameManager != null) && mGameManager.getAngleEnabled(packageName);
+ (mGameManager != null) && mGameManager.isAngleEnabled(packageName);
Log.v(TAG, "ANGLE GameManagerService for " + packageName + ": " + gameModeEnabledAngle);
return gameModeEnabledAngle;
} catch (SecurityException e) {
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index df5b7bc..4fe6524 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -21,15 +21,16 @@
import android.os.ParcelDuration;
import android.os.PowerSaveState;
import android.os.WorkSource;
+import android.os.IWakeLockCallback;
/** @hide */
interface IPowerManager
{
void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, in WorkSource ws,
- String historyTag, int displayId);
+ String historyTag, int displayId, IWakeLockCallback callback);
void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName,
- int uidtoblame, int displayId);
+ int uidtoblame, int displayId, IWakeLockCallback callback);
@UnsupportedAppUsage
void releaseWakeLock(IBinder lock, int flags);
void updateWakeLockUids(IBinder lock, in int[] uids);
@@ -40,6 +41,7 @@
boolean setPowerModeChecked(int mode, boolean enabled);
void updateWakeLockWorkSource(IBinder lock, in WorkSource ws, String historyTag);
+ void updateWakeLockCallback(IBinder lock, IWakeLockCallback callback);
boolean isWakeLockLevelSupported(int level);
void userActivity(int displayId, long time, int event, int flags);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/os/IWakeLockCallback.aidl
similarity index 82%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/os/IWakeLockCallback.aidl
index 861a4ed..89615d2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/os/IWakeLockCallback.aidl
@@ -14,6 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package android.os;
-parcelable DeviceInfo;
+/**
+ * @hide
+ */
+oneway interface IWakeLockCallback {
+ oneway void onStateChanged(boolean enabled);
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 3bc3ec8..9998e12 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -18,6 +18,7 @@
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.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 @@
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 @@
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.
*/
@@ -2088,6 +2144,102 @@
}
/**
+ * Flatten a homogeneous multi-dimensional array with fixed-size. This delegates to other
+ * APIs to write a one-dimensional array. Use {@link #readFixedArray(Object)} or
+ * {@link #createFixedArray(Class, int[])} with the same dimensions to unmarshal.
+ *
+ * @param val The array to be written.
+ * @param parcelableFlags Contextual flags as per
+ * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
+ * Used only if val is an array of Parcelable objects.
+ * @param dimensions an array of int representing length of each dimension. The array should be
+ * sized with the exact size of dimensions.
+ *
+ * @see #readFixedArray
+ * @see #createFixedArray
+ * @see #writeBooleanArray
+ * @see #writeByteArray
+ * @see #writeCharArray
+ * @see #writeIntArray
+ * @see #writeLongArray
+ * @see #writeFloatArray
+ * @see #writeDoubleArray
+ * @see #writeBinderArray
+ * @see #writeInterfaceArray
+ * @see #writeTypedArray
+ * @throws BadParcelableException If the array's component type is not supported or if its
+ * size doesn't match with the given dimensions.
+ */
+ public <T> void writeFixedArray(@Nullable T val, int parcelableFlags,
+ @NonNull int... dimensions) {
+ if (val == null) {
+ writeInt(-1);
+ return;
+ }
+ writeFixedArrayInternal(val, parcelableFlags, /*index=*/0, dimensions);
+ }
+
+ private <T> void writeFixedArrayInternal(T val, int parcelableFlags, int index,
+ int[] dimensions) {
+ if (index >= dimensions.length) {
+ throw new BadParcelableException("Array has more dimensions than expected: "
+ + dimensions.length);
+ }
+
+ int length = dimensions[index];
+
+ // val should be an array of length N
+ if (val == null) {
+ throw new BadParcelableException("Non-null array shouldn't have a null array.");
+ }
+ if (!val.getClass().isArray()) {
+ throw new BadParcelableException("Not an array: " + val);
+ }
+ if (Array.getLength(val) != length) {
+ throw new BadParcelableException("bad length: expected " + length + ", but got "
+ + Array.getLength(val));
+ }
+
+ // Delegates to other writers if this is a one-dimensional array.
+ // Otherwise, write component arrays with recursive calls.
+
+ final Class<?> componentType = val.getClass().getComponentType();
+ if (!componentType.isArray() && index + 1 != dimensions.length) {
+ throw new BadParcelableException("Array has fewer dimensions than expected: "
+ + dimensions.length);
+ }
+ if (componentType == boolean.class) {
+ writeBooleanArray((boolean[]) val);
+ } else if (componentType == byte.class) {
+ writeByteArray((byte[]) val);
+ } else if (componentType == char.class) {
+ writeCharArray((char[]) val);
+ } else if (componentType == int.class) {
+ writeIntArray((int[]) val);
+ } else if (componentType == long.class) {
+ writeLongArray((long[]) val);
+ } else if (componentType == float.class) {
+ writeFloatArray((float[]) val);
+ } else if (componentType == double.class) {
+ writeDoubleArray((double[]) val);
+ } else if (componentType == IBinder.class) {
+ writeBinderArray((IBinder[]) val);
+ } else if (IInterface.class.isAssignableFrom(componentType)) {
+ writeInterfaceArray((IInterface[]) val);
+ } else if (Parcelable.class.isAssignableFrom(componentType)) {
+ writeTypedArray((Parcelable[]) val, parcelableFlags);
+ } else if (componentType.isArray()) {
+ writeInt(length);
+ for (int i = 0; i < length; i++) {
+ writeFixedArrayInternal(Array.get(val, i), parcelableFlags, index + 1,
+ dimensions);
+ }
+ } else {
+ throw new BadParcelableException("unknown type for fixed-size array: " + componentType);
+ }
+ }
+
+ /**
* Flatten a generic object in to a parcel. The given Object value may
* currently be one of the following types:
*
@@ -2949,7 +3101,15 @@
* 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;
}
/**
@@ -3788,6 +3948,317 @@
}
/**
+ * Read a new multi-dimensional array from a parcel. If you want to read Parcelable or
+ * IInterface values, use {@link #readFixedArray(Object, Parcelable.Creator)} or
+ * {@link #readFixedArray(Object, Function)}.
+ * @param val the destination array to hold the read values.
+ *
+ * @see #writeTypedArray
+ * @see #readBooleanArray
+ * @see #readByteArray
+ * @see #readCharArray
+ * @see #readIntArray
+ * @see #readLongArray
+ * @see #readFloatArray
+ * @see #readDoubleArray
+ * @see #readBinderArray
+ * @see #readInterfaceArray
+ * @see #readTypedArray
+ */
+ public <T> void readFixedArray(@NonNull T val) {
+ Class<?> componentType = val.getClass().getComponentType();
+ if (componentType == boolean.class) {
+ readBooleanArray((boolean[]) val);
+ } else if (componentType == byte.class) {
+ readByteArray((byte[]) val);
+ } else if (componentType == char.class) {
+ readCharArray((char[]) val);
+ } else if (componentType == int.class) {
+ readIntArray((int[]) val);
+ } else if (componentType == long.class) {
+ readLongArray((long[]) val);
+ } else if (componentType == float.class) {
+ readFloatArray((float[]) val);
+ } else if (componentType == double.class) {
+ readDoubleArray((double[]) val);
+ } else if (componentType == IBinder.class) {
+ readBinderArray((IBinder[]) val);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length != Array.getLength(val)) {
+ throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
+ + ", but got " + length);
+ }
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i));
+ }
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+ }
+
+ /**
+ * Read a new multi-dimensional array of typed interfaces from a parcel.
+ * If you want to read Parcelable values, use
+ * {@link #readFixedArray(Object, Parcelable.Creator)}. For values of other types, use
+ * {@link #readFixedArray(Object)}.
+ * @param val the destination array to hold the read values.
+ */
+ public <T, S extends IInterface> void readFixedArray(@NonNull T val,
+ @NonNull Function<IBinder, S> asInterface) {
+ Class<?> componentType = val.getClass().getComponentType();
+ if (IInterface.class.isAssignableFrom(componentType)) {
+ readInterfaceArray((S[]) val, asInterface);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length != Array.getLength(val)) {
+ throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
+ + ", but got " + length);
+ }
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i), asInterface);
+ }
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+ }
+
+ /**
+ * Read a new multi-dimensional array of typed parcelables from a parcel.
+ * If you want to read IInterface values, use
+ * {@link #readFixedArray(Object, Function)}. For values of other types, use
+ * {@link #readFixedArray(Object)}.
+ * @param val the destination array to hold the read values.
+ */
+ public <T, S extends Parcelable> void readFixedArray(@NonNull T val,
+ @NonNull Parcelable.Creator<S> c) {
+ Class<?> componentType = val.getClass().getComponentType();
+ if (Parcelable.class.isAssignableFrom(componentType)) {
+ readTypedArray((S[]) val, c);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length != Array.getLength(val)) {
+ throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
+ + ", but got " + length);
+ }
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i), c);
+ }
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+ }
+
+ private void ensureClassHasExpectedDimensions(@NonNull Class<?> cls, int numDimension) {
+ if (numDimension <= 0) {
+ throw new BadParcelableException("Fixed-size array should have dimensions.");
+ }
+
+ for (int i = 0; i < numDimension; i++) {
+ if (!cls.isArray()) {
+ throw new BadParcelableException("Array has fewer dimensions than expected: "
+ + numDimension);
+ }
+ cls = cls.getComponentType();
+ }
+ if (cls.isArray()) {
+ throw new BadParcelableException("Array has more dimensions than expected: "
+ + numDimension);
+ }
+ }
+
+ /**
+ * Read and return a new multi-dimensional array from a parcel. Returns null if the
+ * previously written array object is null. If you want to read Parcelable or
+ * IInterface values, use {@link #createFixedArray(Class, Parcelable.Creator, int[])} or
+ * {@link #createFixedArray(Class, Function, int[])}.
+ * @param cls the Class object for the target array type. (e.g. int[][].class)
+ * @param dimensions an array of int representing length of each dimension.
+ *
+ * @see #writeTypedArray
+ * @see #createBooleanArray
+ * @see #createByteArray
+ * @see #createCharArray
+ * @see #createIntArray
+ * @see #createLongArray
+ * @see #createFloatArray
+ * @see #createDoubleArray
+ * @see #createBinderArray
+ * @see #createInterfaceArray
+ * @see #createTypedArray
+ */
+ @Nullable
+ public <T> T createFixedArray(@NonNull Class<T> cls, @NonNull int... dimensions) {
+ // Check if type matches with dimensions
+ // If type is one-dimensional array, delegate to other creators
+ // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
+
+ ensureClassHasExpectedDimensions(cls, dimensions.length);
+
+ T val = null;
+ final Class<?> componentType = cls.getComponentType();
+ if (componentType == boolean.class) {
+ val = (T) createBooleanArray();
+ } else if (componentType == byte.class) {
+ val = (T) createByteArray();
+ } else if (componentType == char.class) {
+ val = (T) createCharArray();
+ } else if (componentType == int.class) {
+ val = (T) createIntArray();
+ } else if (componentType == long.class) {
+ val = (T) createLongArray();
+ } else if (componentType == float.class) {
+ val = (T) createFloatArray();
+ } else if (componentType == double.class) {
+ val = (T) createDoubleArray();
+ } else if (componentType == IBinder.class) {
+ val = (T) createBinderArray();
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length < 0) {
+ return null;
+ }
+ if (length != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0]
+ + ", but got " + length);
+ }
+
+ // Create a multi-dimensional array with an innermost component type and dimensions
+ Class<?> innermost = componentType.getComponentType();
+ while (innermost.isArray()) {
+ innermost = innermost.getComponentType();
+ }
+ val = (T) Array.newInstance(innermost, dimensions);
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i));
+ }
+ return val;
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+
+ // Check if val is null (which is OK) or has the expected size.
+ // This check doesn't have to be multi-dimensional because multi-dimensional arrays
+ // are created with expected dimensions.
+ if (val != null && Array.getLength(val) != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
+ + Array.getLength(val));
+ }
+ return val;
+ }
+
+ /**
+ * Read and return a new multi-dimensional array of typed interfaces from a parcel.
+ * Returns null if the previously written array object is null. If you want to read
+ * Parcelable values, use {@link #createFixedArray(Class, Parcelable.Creator, int[])}.
+ * For values of other types use {@link #createFixedArray(Class, int[])}.
+ * @param cls the Class object for the target array type. (e.g. IFoo[][].class)
+ * @param dimensions an array of int representing length of each dimension.
+ */
+ @Nullable
+ public <T, S extends IInterface> T createFixedArray(@NonNull Class<T> cls,
+ @NonNull Function<IBinder, S> asInterface, @NonNull int... dimensions) {
+ // Check if type matches with dimensions
+ // If type is one-dimensional array, delegate to other creators
+ // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
+
+ ensureClassHasExpectedDimensions(cls, dimensions.length);
+
+ T val = null;
+ final Class<?> componentType = cls.getComponentType();
+ if (IInterface.class.isAssignableFrom(componentType)) {
+ val = (T) createInterfaceArray(n -> (S[]) Array.newInstance(componentType, n),
+ asInterface);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length < 0) {
+ return null;
+ }
+ if (length != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0]
+ + ", but got " + length);
+ }
+
+ // Create a multi-dimensional array with an innermost component type and dimensions
+ Class<?> innermost = componentType.getComponentType();
+ while (innermost.isArray()) {
+ innermost = innermost.getComponentType();
+ }
+ val = (T) Array.newInstance(innermost, dimensions);
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i), asInterface);
+ }
+ return val;
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+
+ // Check if val is null (which is OK) or has the expected size.
+ // This check doesn't have to be multi-dimensional because multi-dimensional arrays
+ // are created with expected dimensions.
+ if (val != null && Array.getLength(val) != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
+ + Array.getLength(val));
+ }
+ return val;
+ }
+
+ /**
+ * Read and return a new multi-dimensional array of typed parcelables from a parcel.
+ * Returns null if the previously written array object is null. If you want to read
+ * IInterface values, use {@link #createFixedArray(Class, Function, int[])}.
+ * For values of other types use {@link #createFixedArray(Class, int[])}.
+ * @param cls the Class object for the target array type. (e.g. Foo[][].class)
+ * @param dimensions an array of int representing length of each dimension.
+ */
+ @Nullable
+ public <T, S extends Parcelable> T createFixedArray(@NonNull Class<T> cls,
+ @NonNull Parcelable.Creator<S> c, @NonNull int... dimensions) {
+ // Check if type matches with dimensions
+ // If type is one-dimensional array, delegate to other creators
+ // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
+
+ ensureClassHasExpectedDimensions(cls, dimensions.length);
+
+ T val = null;
+ final Class<?> componentType = cls.getComponentType();
+ if (Parcelable.class.isAssignableFrom(componentType)) {
+ val = (T) createTypedArray(c);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length < 0) {
+ return null;
+ }
+ if (length != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0]
+ + ", but got " + length);
+ }
+
+ // Create a multi-dimensional array with an innermost component type and dimensions
+ Class<?> innermost = componentType.getComponentType();
+ while (innermost.isArray()) {
+ innermost = innermost.getComponentType();
+ }
+ val = (T) Array.newInstance(innermost, dimensions);
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i), c);
+ }
+ return val;
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+
+ // Check if val is null (which is OK) or has the expected size.
+ // This check doesn't have to be multi-dimensional because multi-dimensional arrays
+ // are created with expected dimensions.
+ if (val != null && Array.getLength(val) != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
+ + Array.getLength(val));
+ }
+ return val;
+ }
+
+ /**
* Write a heterogeneous array of Parcelable objects into the Parcel.
* Each object in the array is written along with its class name, so
* that the correct class can later be instantiated. As a result, this
@@ -4588,6 +5059,7 @@
}
private void freeBuffer() {
+ mFlags = 0;
resetSqaushingState();
if (mOwnsNativeParcelObject) {
nativeFreeBuffer(mNativePtr);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 5bd8588..315eef7 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -2770,6 +2770,23 @@
public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2;
/**
+ * A listener interface to get notified when the wakelock is enabled/disabled.
+ */
+ public interface WakeLockStateListener {
+ /**
+ * Frameworks could disable the wakelock because either device's power allowlist has
+ * changed, or the app's wakelock has exceeded its quota, or the app goes into cached
+ * state.
+ * <p>
+ * This callback is called whenever the wakelock's state has changed.
+ * </p>
+ *
+ * @param enabled true is enabled, false is disabled.
+ */
+ void onStateChanged(boolean enabled);
+ }
+
+ /**
* A wake lock is a mechanism to indicate that your application needs
* to have the device stay on.
* <p>
@@ -2800,6 +2817,8 @@
private String mHistoryTag;
private final String mTraceName;
private final int mDisplayId;
+ private WakeLockStateListener mListener;
+ private IWakeLockCallback mCallback;
private final Runnable mReleaser = () -> release(RELEASE_FLAG_TIMEOUT);
@@ -2890,7 +2909,7 @@
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
try {
mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
- mHistoryTag, mDisplayId);
+ mHistoryTag, mDisplayId, mCallback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3083,6 +3102,45 @@
}
};
}
+
+ /**
+ * Set the listener to get notified when the wakelock is enabled/disabled.
+ *
+ * @param executor {@link Executor} to handle listener callback.
+ * @param listener listener to be added, set the listener to null to cancel a listener.
+ */
+ public void setStateListener(@NonNull @CallbackExecutor Executor executor,
+ @Nullable WakeLockStateListener listener) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ synchronized (mToken) {
+ if (listener != mListener) {
+ mListener = listener;
+ if (listener != null) {
+ mCallback = new IWakeLockCallback.Stub() {
+ public void onStateChanged(boolean enabled) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> {
+ listener.onStateChanged(enabled);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+ } else {
+ mCallback = null;
+ }
+ if (mHeld) {
+ try {
+ mService.updateWakeLockCallback(mToken, mCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ }
}
/**
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 49c0520..d223a19 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -106,20 +106,28 @@
public static final int USAGE_NOTIFICATION = 0x30 | USAGE_CLASS_ALARM;
/**
* Usage value to use for vibrations which mean a request to enter/end a
- * communication, such as a VoIP communication or video-conference.
+ * communication with the user, such as a voice prompt.
*/
public static final int USAGE_COMMUNICATION_REQUEST = 0x40 | USAGE_CLASS_ALARM;
/**
* Usage value to use for touch vibrations.
+ *
+ * <p>Most typical haptic feedback should be classed as <em>touch</em> feedback. Examples
+ * include vibrations for tap, long press, drag and scroll.
*/
public static final int USAGE_TOUCH = 0x10 | USAGE_CLASS_FEEDBACK;
/**
- * Usage value to use for vibrations which emulate physical effects, such as edge squeeze.
+ * Usage value to use for vibrations which emulate physical hardware reactions,
+ * such as edge squeeze.
+ *
+ * <p>Note that normal screen-touch feedback "click" effects would typically be
+ * classed as {@link #USAGE_TOUCH}, and that on-screen "physical" animations
+ * like bouncing would be {@link #USAGE_MEDIA}.
*/
public static final int USAGE_PHYSICAL_EMULATION = 0x20 | USAGE_CLASS_FEEDBACK;
/**
- * Usage value to use for vibrations which provide a feedback for hardware interaction,
- * such as a fingerprint sensor.
+ * Usage value to use for vibrations which provide a feedback for hardware
+ * component interaction, such as a fingerprint sensor.
*/
public static final int USAGE_HARDWARE_FEEDBACK = 0x30 | USAGE_CLASS_FEEDBACK;
/**
@@ -183,7 +191,6 @@
/**
* Return the vibration usage class.
- * @return USAGE_CLASS_ALARM, USAGE_CLASS_FEEDBACK or USAGE_CLASS_UNKNOWN
*/
@UsageClass
public int getUsageClass() {
@@ -192,7 +199,6 @@
/**
* Return the vibration usage.
- * @return one of the values that can be set in {@link Builder#setUsage(int)}
*/
@Usage
public int getUsage() {
@@ -429,16 +435,8 @@
}
/**
- * Sets the attribute describing the type of corresponding vibration.
- * @param usage one of {@link VibrationAttributes#USAGE_ALARM},
- * {@link VibrationAttributes#USAGE_RINGTONE},
- * {@link VibrationAttributes#USAGE_NOTIFICATION},
- * {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST},
- * {@link VibrationAttributes#USAGE_TOUCH},
- * {@link VibrationAttributes#USAGE_PHYSICAL_EMULATION},
- * {@link VibrationAttributes#USAGE_HARDWARE_FEEDBACK}.
- * {@link VibrationAttributes#USAGE_ACCESSIBILITY}.
- * {@link VibrationAttributes#USAGE_MEDIA}.
+ * Sets the attribute describing the type of the corresponding vibration.
+ * @param usage The type of usage for the vibration
* @return the same Builder instance.
*/
public @NonNull Builder setUsage(@Usage int usage) {
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index f490587..21c6487 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -194,27 +194,27 @@
}
/**
- * Create a waveform vibration.
+ * Create a waveform vibration, using only off/on transitions at the provided time intervals,
+ * and potentially repeating.
*
- * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
- * each pair, the value in the amplitude array determines the strength of the vibration and the
- * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
- * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+ * <p>In effect, the timings array represents the number of milliseconds <em>before</em> turning
+ * the vibrator on, followed by the number of milliseconds to keep the vibrator on, then
+ * the number of milliseconds turned off, and so on. Consequently, the first timing value will
+ * often be 0, so that the effect will start vibrating immediately.
*
- * <p>The amplitude array of the generated waveform will be the same size as the given
- * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
- * starting with 0. Therefore the first timing value will be the period to wait before turning
- * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
- * strength, etc.
+ * <p>This method is equivalent to calling {@link #createWaveform(long[], int[], int)} with
+ * corresponding amplitude values alternating between 0 and {@link #DEFAULT_AMPLITUDE},
+ * beginning with 0.
*
* <p>To cause the pattern to repeat, pass the index into the timings array at which to start
* the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely
* and should be cancelled via {@link Vibrator#cancel()}.
*
- * @param timings The pattern of alternating on-off timings, starting with off. Timing values
- * of 0 will cause the timing / amplitude pair to be ignored.
- * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
- * want to repeat.
+ * @param timings The pattern of alternating on-off timings, starting with an 'off' timing, and
+ * representing the length of time to sustain the individual item (not
+ * cumulative).
+ * @param repeat The index into the timings array at which to repeat, or -1 if you don't
+ * want to repeat indefinitely.
*
* @return The desired effect.
*/
@@ -229,11 +229,10 @@
/**
* Create a waveform vibration.
*
- * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
- * each pair, the value in the amplitude array determines the strength of the vibration and the
- * value in the timing array determines how long it vibrates for, in milliseconds. Amplitude
- * values must be between 0 and 255, and an amplitude of 0 implies no vibration (i.e. off). Any
- * pairs with a timing value of 0 will be ignored.
+ * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs,
+ * provided in separate arrays. For each pair, the value in the amplitude array determines
+ * the strength of the vibration and the value in the timing array determines how long it
+ * vibrates for, in milliseconds.
*
* <p>To cause the pattern to repeat, pass the index into the timings array at which to start
* the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely
@@ -244,8 +243,8 @@
* @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
* must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
* amplitude value of 0 implies the motor is off.
- * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
- * want to repeat.
+ * @param repeat The index into the timings array at which to repeat, or -1 if you don't
+ * want to repeat indefinitely.
*
* @return The desired effect.
*/
@@ -411,9 +410,9 @@
*
* <p>The waveform will start the first transition from the vibrator off state, with the
* resonant frequency by default. To provide an initial state, use
- * {@link #startWaveform(VibrationParameter)}.
+ * {@link #startWaveform(VibrationEffect.VibrationParameter)}.
*
- * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+ * @see VibrationEffect.WaveformBuilder
*/
@NonNull
public static WaveformBuilder startWaveform() {
@@ -422,14 +421,16 @@
/**
* Start building a waveform vibration with an initial state specified by a
- * {@link VibrationParameter}.
+ * {@link VibrationEffect.VibrationParameter}.
*
* <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
* control over vibration amplitude and frequency via smooth transitions between values.
*
- * @param initialParameter The initial {@link VibrationParameter} value to be applied at the
- * beginning of the vibration.
+ * @param initialParameter The initial {@link VibrationEffect.VibrationParameter} value to be
+ * applied at the beginning of the vibration.
* @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+ *
+ * @see VibrationEffect.WaveformBuilder
*/
@NonNull
public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter) {
@@ -440,17 +441,19 @@
/**
* Start building a waveform vibration with an initial state specified by two
- * {@link VibrationParameter VibrationParameters}.
+ * {@link VibrationEffect.VibrationParameter VibrationParameters}.
*
* <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
* control over vibration amplitude and frequency via smooth transitions between values.
*
- * @param initialParameter1 The initial {@link VibrationParameter} value to be applied at the
- * beginning of the vibration.
- * @param initialParameter2 The initial {@link VibrationParameter} value to be applied at the
- * beginning of the vibration, must be a different type of parameter
- * than the one specified by the first argument.
+ * @param initialParameter1 The initial {@link VibrationEffect.VibrationParameter} value to be
+ * applied at the beginning of the vibration.
+ * @param initialParameter2 The initial {@link VibrationEffect.VibrationParameter} value to be
+ * applied at the beginning of the vibration, must be a different type
+ * of parameter than the one specified by the first argument.
* @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+ *
+ * @see VibrationEffect.WaveformBuilder
*/
@NonNull
public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter1,
@@ -806,7 +809,46 @@
}
/**
- * A composition of haptic primitives that, when combined, create a single haptic effect.
+ * A composition of haptic elements that are combined to be playable as a single
+ * {@link VibrationEffect}.
+ *
+ * <p>The haptic primitives are available as {@code Composition.PRIMITIVE_*} constants and
+ * can be added to a composition to create a custom vibration effect. Here is an example of an
+ * effect that grows in intensity and then dies off, with a longer rising portion for emphasis
+ * and an extra tick 100ms after:
+ *
+ * <code>
+ * VibrationEffect effect = VibrationEffect.startComposition()
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f)
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f)
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100)
+ * .compose();
+ * </code>
+ *
+ * <p>Composition elements can also be {@link VibrationEffect} instances, including other
+ * compositions, and off durations, which are periods of time when the vibrator will be
+ * turned off. Here is an example of a composition that "warms up" with a light tap,
+ * a stronger double tap, then repeats a vibration pattern indefinitely:
+ *
+ * <code>
+ * VibrationEffect repeatingEffect = VibrationEffect.startComposition()
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
+ * .addOffDuration(Duration.ofMillis(10))
+ * .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK))
+ * .addOffDuration(Duration.ofMillis(50))
+ * .addEffect(VibrationEffect.createWaveform(pattern, repeatIndex))
+ * .compose();
+ * </code>
+ *
+ * <p>When choosing to play a composed effect, you should check that individual components are
+ * supported by the device by using the appropriate vibrator method:
+ *
+ * <ul>
+ * <li>Primitive support can be checked using {@link Vibrator#arePrimitivesSupported}.
+ * <li>Effect support can be checked using {@link Vibrator#areEffectsSupported}.
+ * <li>Amplitude control for one-shot and waveforms with amplitude values can be checked
+ * using {@link Vibrator#hasAmplitudeControl}.
+ * </ul>
*
* @see VibrationEffect#startComposition()
*/
@@ -1092,16 +1134,77 @@
* A builder for waveform haptic effects.
*
* <p>Waveform vibrations constitute of one or more timed transitions to new sets of vibration
- * parameters. These parameters can be the vibration amplitude or frequency, for example.
+ * parameters. These parameters can be the vibration amplitude, frequency, or both.
+ *
+ * <p>The following example ramps a vibrator turned off to full amplitude at 120Hz, over 100ms
+ * starting at 60Hz, then holds that state for 200ms and ramps back down again over 100ms:
+ *
+ * <code>
+ * import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
+ * import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
+ *
+ * VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60))
+ * .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+ * .addSustain(Duration.ofMillis(200))
+ * .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+ * .build();
+ * </code>
+ *
+ * <p>The initial state of the waveform can be set via
+ * {@link VibrationEffect#startWaveform(VibrationParameter)} or
+ * {@link VibrationEffect#startWaveform(VibrationParameter, VibrationParameter)}. If the initial
+ * parameters are not set then the {@link WaveformBuilder} will start with the vibrator off,
+ * represented by zero amplitude, at the vibrator's resonant frequency.
+ *
+ * <p>Repeating waveforms can be created by building the repeating block separately and adding
+ * it to the end of a composition with
+ * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}:
*
* <p>Note that physical vibration actuators have different reaction times for changing
* amplitude and frequency. Durations specified here represent a timeline for the target
* parameters, and quality of effects may be improved if the durations allow time for a
* transition to be smoothly applied.
*
- * <p>Repeating waveforms can be built by constructing the repeating block separately and adding
- * it to the end of a composition using
- * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}.
+ * <p>The following example illustrates both an initial state and a repeating section, using
+ * a {@link VibrationEffect.Composition}. The resulting effect will have a tick followed by a
+ * repeated beating effect with a rise that stretches out and a sharp finish.
+ *
+ * <code>
+ * VibrationEffect patternToBeRepeated = VibrationEffect.startWaveform(targetAmplitude(0.2f))
+ * .addSustain(Duration.ofMillis(10))
+ * .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f))
+ * .addSustain(Duration.ofMillis(30))
+ * .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f))
+ * .addSustain(Duration.ofMillis(50))
+ * .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f))
+ * .build();
+ *
+ * VibrationEffect effect = VibrationEffect.startComposition()
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ * .addOffDuration(Duration.ofMillis(20))
+ * .repeatEffectIndefinitely(patternToBeRepeated)
+ * .compose();
+ * </code>
+ *
+ * <p>The amplitude step waveforms that can be created via
+ * {@link VibrationEffect#createWaveform(long[], int[], int)} can also be created with
+ * {@link WaveformBuilder} by adding zero duration transitions:
+ *
+ * <code>
+ * // These two effects are the same
+ * VibrationEffect waveform = VibrationEffect.createWaveform(
+ * new long[] { 10, 20, 30 }, // timings in milliseconds
+ * new int[] { 51, 102, 204 }, // amplitudes in [0,255]
+ * -1); // repeat index
+ *
+ * VibrationEffect sameWaveform = VibrationEffect.startWaveform(targetAmplitude(0.2f))
+ * .addSustain(Duration.ofMillis(10))
+ * .addTransition(Duration.ZERO, targetAmplitude(0.4f))
+ * .addSustain(Duration.ofMillis(20))
+ * .addTransition(Duration.ZERO, targetAmplitude(0.8f))
+ * .addSustain(Duration.ofMillis(30))
+ * .build();
+ * </code>
*
* @see VibrationEffect#startWaveform
*/
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 23baa5d..78f1cb1 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -98,7 +98,8 @@
/**
* Vibration effect support: unsupported
*
- * This effect is <b>not</b> supported by the underlying hardware.
+ * This effect is <b>not</b> natively supported by the underlying hardware, although
+ * the system may still play a fallback vibration.
*/
public static final int VIBRATION_EFFECT_SUPPORT_NO = 2;
@@ -485,20 +486,25 @@
String reason, @NonNull VibrationAttributes attributes);
/**
- * Query whether the vibrator supports the given effects.
+ * Query whether the vibrator natively supports the given effects.
*
- * Not all hardware reports its effect capabilities, so the system may not necessarily know
- * whether an effect is supported or not.
+ * <p>If an effect is not supported, the system may still automatically fall back to playing
+ * a simpler vibration instead, which is not optimised for the specific device. This includes
+ * the unknown case, which can't be determined in advance, that will dynamically attempt to
+ * fall back if the optimised effect fails to play.
*
- * The returned array will be the same length as the query array and the value at a given index
- * will contain {@link #VIBRATION_EFFECT_SUPPORT_YES} if the effect at that same index in the
- * querying array is supported, {@link #VIBRATION_EFFECT_SUPPORT_NO} if it isn't supported, or
- * {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN} if the system can't determine whether it's
- * supported or not.
+ * <p>The returned array will be the same length as the query array and the value at a given
+ * index will contain {@link #VIBRATION_EFFECT_SUPPORT_YES} if the effect at that same index
+ * in the querying array is supported, {@link #VIBRATION_EFFECT_SUPPORT_NO} if it isn't
+ * supported, or {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN} if the system can't determine whether
+ * it's supported or not, as some hardware doesn't report its effect capabilities.
+ *
+ * <p>Use {@link #areAllEffectsSupported(int...)} to get a single combined result,
+ * or for convenience when querying exactly one effect.
*
* @param effectIds Which effects to query for.
* @return An array containing the systems current knowledge about whether the given effects
- * are supported or not.
+ * are natively supported by the device, or not.
*/
@NonNull
@VibrationEffectSupport
@@ -515,39 +521,47 @@
/**
* Query whether the vibrator supports all of the given effects.
*
- * Not all hardware reports its effect capabilities, so the system may not necessarily know
- * whether an effect is supported or not.
+ * <p>If an effect is not supported, the system may still automatically fall back to a simpler
+ * vibration instead, which is not optimised for the specific device, however vibration isn't
+ * guaranteed in this case.
*
- * If the result is {@link #VIBRATION_EFFECT_SUPPORT_YES}, all effects in the query are
+ * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_YES}, all effects in the query are
* supported by the hardware.
*
- * If the result is {@link #VIBRATION_EFFECT_SUPPORT_NO}, at least one of the effects in the
- * query is not supported.
+ * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_NO}, at least one of the effects in the
+ * query is not supported, and using them may fall back to an un-optimized vibration or no
+ * vibration.
*
- * If the result is {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN}, the system doesn't know whether
- * all of the effects are supported. It may support any or all of the queried effects,
+ * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN}, the system doesn't know
+ * whether all of the effects are supported. It may support any or all of the queried effects,
* but there's no way to programmatically know whether a {@link #vibrate} call will successfully
* cause a vibration. It's guaranteed, however, that none of the queried effects are
* definitively unsupported by the hardware.
*
+ * <p>Use {@link #areEffectsSupported(int...)} to get individual results for each effect.
+ *
* @param effectIds Which effects to query for.
- * @return Whether all of the effects are supported.
+ * @return Whether all of the effects are natively supported by the device.
*/
@VibrationEffectSupport
public final int areAllEffectsSupported(
@NonNull @VibrationEffect.EffectType int... effectIds) {
- int support = VIBRATION_EFFECT_SUPPORT_YES;
- for (int supported : areEffectsSupported(effectIds)) {
- if (supported == VIBRATION_EFFECT_SUPPORT_NO) {
- return VIBRATION_EFFECT_SUPPORT_NO;
- } else if (supported == VIBRATION_EFFECT_SUPPORT_UNKNOWN) {
- support = VIBRATION_EFFECT_SUPPORT_UNKNOWN;
+ VibratorInfo info = getInfo();
+ int allSupported = VIBRATION_EFFECT_SUPPORT_YES;
+ for (int effectId : effectIds) {
+ switch (info.isEffectSupported(effectId)) {
+ case VIBRATION_EFFECT_SUPPORT_NO:
+ return VIBRATION_EFFECT_SUPPORT_NO;
+ case VIBRATION_EFFECT_SUPPORT_YES:
+ continue;
+ default: // VIBRATION_EFFECT_SUPPORT_UNKNOWN
+ allSupported = VIBRATION_EFFECT_SUPPORT_UNKNOWN;
+ break;
}
}
- return support;
+ return allSupported;
}
-
/**
* Query whether the vibrator supports the given primitives.
*
@@ -555,6 +569,12 @@
* will contain whether the effect at that same index in the querying array is supported or
* not.
*
+ * <p>If a primitive is not supported by the device, then <em>no vibration</em> will occur if
+ * it is played.
+ *
+ * <p>Use {@link #areAllPrimitivesSupported(int...)} to get a single combined result,
+ * or for convenience when querying exactly one primitive.
+ *
* @param primitiveIds Which primitives to query for.
* @return Whether the primitives are supported.
*/
@@ -572,13 +592,19 @@
/**
* Query whether the vibrator supports all of the given primitives.
*
+ * <p>If a primitive is not supported by the device, then <em>no vibration</em> will occur if
+ * it is played.
+ *
+ * <p>Use {@link #arePrimitivesSupported(int...)} to get individual results for each primitive.
+ *
* @param primitiveIds Which primitives to query for.
- * @return Whether primitives effects are supported.
+ * @return Whether all specified primitives are supported.
*/
public final boolean areAllPrimitivesSupported(
@NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
- for (boolean supported : arePrimitivesSupported(primitiveIds)) {
- if (!supported) {
+ VibratorInfo info = getInfo();
+ for (int primitiveId : primitiveIds) {
+ if (!info.isPrimitiveSupported(primitiveId)) {
return false;
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 14055ac..ee6f9c0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2436,6 +2436,23 @@
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_MMS_MESSAGE_SETTING = "android.settings.MMS_MESSAGE_SETTING";
+ /**
+ * Activity Action: Show a screen of bedtime settings, which is provided by the wellbeing app.
+ * <p>
+ * The handler of this intent action may not exist.
+ * <p>
+ * To start an activity with this intent, apps should set the wellbeing package explicitly in
+ * the intent together with this action. The wellbeing package is defined in
+ * {@code com.android.internal.R.string.config_defaultWellbeingPackage}.
+ * <p>
+ * Output: Nothing
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_BEDTIME_SETTINGS = "android.settings.BEDTIME_SETTINGS";
+
// End of Intent actions for Settings
/**
@@ -6077,9 +6094,11 @@
}
/** @hide */
- @UnsupportedAppUsage
- public static String getStringForUser(ContentResolver resolver, String name,
- int userHandle) {
+ @SystemApi
+ @Nullable
+ @SuppressLint("VisiblySynchronized")
+ public static String getStringForUser(@NonNull ContentResolver resolver,
+ @NonNull String name, int userHandle) {
if (MOVED_TO_GLOBAL.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure"
+ " to android.provider.Settings.Global.");
@@ -6311,8 +6330,9 @@
}
/** @hide */
- @UnsupportedAppUsage
- public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
+ @SystemApi
+ public static int getIntForUser(@NonNull ContentResolver cr, @NonNull String name,
+ int def, int userHandle) {
String v = getStringForUser(cr, name, userHandle);
return parseIntSettingWithDefault(v, def);
}
@@ -10048,6 +10068,13 @@
public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles";
/**
+ * The duration of timeout, in milliseconds, to switch from a non-primary user to the
+ * primary user when the device is docked.
+ * @hide
+ */
+ public static final String TIMEOUT_TO_USER_ZERO = "timeout_to_user_zero";
+
+ /**
* Backup manager behavioral parameters.
* This is encoded as a key=value list, separated by commas. Ex:
*
@@ -10281,6 +10308,15 @@
public static final String NEARBY_SHARING_SLICE_URI = "nearby_sharing_slice_uri";
/**
+ * Current provider of Fast Pair saved devices page.
+ * Default value in @string/config_defaultNearbyFastPairSettingsDevicesComponent.
+ * No VALIDATOR as this setting will not be backed up.
+ * @hide
+ */
+ public static final String NEARBY_FAST_PAIR_SETTINGS_DEVICES_COMPONENT =
+ "nearby_fast_pair_settings_devices_component";
+
+ /**
* Controls whether aware is enabled.
* @hide
*/
@@ -10632,6 +10668,15 @@
public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled";
/**
+ * Setting to store denylisted system languages by the CEC {@code <Set Menu Language>}
+ * confirmation dialog.
+ *
+ * @hide
+ */
+ public static final String HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST =
+ "hdmi_cec_set_menu_language_denylist";
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
*/
diff --git a/core/java/android/service/attention/AttentionService.java b/core/java/android/service/attention/AttentionService.java
index 49ab5db..f5c59b5 100644
--- a/core/java/android/service/attention/AttentionService.java
+++ b/core/java/android/service/attention/AttentionService.java
@@ -24,11 +24,14 @@
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Slog;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.Objects;
/**
@@ -51,6 +54,7 @@
*/
@SystemApi
public abstract class AttentionService extends Service {
+ private static final String LOG_TAG = "AttentionService";
/**
* The {@link Intent} that must be declared as handled by the service. To be supported, the
* service must also require the {@link android.Manifest.permission#BIND_ATTENTION_SERVICE}
@@ -80,6 +84,9 @@
/** Camera permission is not granted. */
public static final int ATTENTION_FAILURE_CAMERA_PERMISSION_ABSENT = 6;
+ /** Users’ proximity is unknown (proximity sensing was inconclusive and is unsupported). */
+ public static final double PROXIMITY_UNKNOWN = -1;
+
/**
* Result codes for when attention check was successful.
*
@@ -118,6 +125,20 @@
Preconditions.checkNotNull(callback);
AttentionService.this.onCancelAttentionCheck(new AttentionCallback(callback));
}
+
+ /** {@inheritDoc} */
+ @Override
+ public void onStartProximityUpdates(IProximityCallback callback) {
+ Objects.requireNonNull(callback);
+ AttentionService.this.onStartProximityUpdates(new ProximityCallback(callback));
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onStopProximityUpdates() {
+ AttentionService.this.onStopProximityUpdates();
+ }
};
@Nullable
@@ -143,6 +164,23 @@
*/
public abstract void onCancelAttentionCheck(@NonNull AttentionCallback callback);
+ /**
+ * Requests the continuous updates of proximity signal via the provided callback,
+ * until the given callback is unregistered.
+ *
+ * @param callback the callback to return the result to
+ */
+ public void onStartProximityUpdates(@NonNull ProximityCallback callback) {
+ Slog.w(LOG_TAG, "Override this method.");
+ }
+
+ /**
+ * Requests to stop providing continuous updates until the callback is registered.
+ */
+ public void onStopProximityUpdates() {
+ Slog.w(LOG_TAG, "Override this method.");
+ }
+
/** Callbacks for AttentionService results. */
public static final class AttentionCallback {
@NonNull private final IAttentionCallback mCallback;
@@ -174,4 +212,26 @@
}
}
}
+
+ /** Callbacks for ProximityCallback results. */
+ public static final class ProximityCallback {
+ @NonNull private final WeakReference<IProximityCallback> mCallback;
+
+ private ProximityCallback(@NonNull IProximityCallback callback) {
+ mCallback = new WeakReference<>(callback);
+ }
+
+ /**
+ * @param distance the estimated distance of the user (in meter)
+ * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive.
+ *
+ */
+ public void onProximityUpdate(double distance) {
+ try {
+ mCallback.get().onProximityUpdate(distance);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
}
diff --git a/core/java/android/service/attention/IAttentionService.aidl b/core/java/android/service/attention/IAttentionService.aidl
index 99e7997..8bb881b 100644
--- a/core/java/android/service/attention/IAttentionService.aidl
+++ b/core/java/android/service/attention/IAttentionService.aidl
@@ -17,6 +17,7 @@
package android.service.attention;
import android.service.attention.IAttentionCallback;
+import android.service.attention.IProximityCallback;
/**
* Interface for a concrete implementation to provide to the AttentionManagerService.
@@ -26,4 +27,6 @@
oneway interface IAttentionService {
void checkAttention(IAttentionCallback callback);
void cancelAttentionCheck(IAttentionCallback callback);
+ void onStartProximityUpdates(IProximityCallback callback);
+ void onStopProximityUpdates();
}
\ No newline at end of file
diff --git a/core/java/android/service/attention/IProximityCallback.aidl b/core/java/android/service/attention/IProximityCallback.aidl
new file mode 100644
index 0000000..9ecf9bc
--- /dev/null
+++ b/core/java/android/service/attention/IProximityCallback.aidl
@@ -0,0 +1,10 @@
+package android.service.attention;
+
+/**
+ * Callback for onStartProximityUpdates request.
+ *
+ * @hide
+ */
+oneway interface IProximityCallback {
+ void onProximityUpdate(double distance);
+}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 29c7796..cb1b5d3 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -578,6 +578,14 @@
public static final String SERVICE_META_DATA = "android.autofill";
/**
+ * Name of the {@link FillResponse} extra used to return a delayed fill response.
+ *
+ * <p>Please see {@link FillRequest#getDelayedFillIntentSender()} on how to send a delayed
+ * fill response to framework.</p>
+ */
+ public static final String EXTRA_FILL_RESPONSE = "android.service.autofill.extra.FILL_RESPONSE";
+
+ /**
* Name of the {@link IResultReceiver} extra used to return the primary result of a request.
*
* @hide
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index f820f03..e4d3732 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.IntentSender;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -160,6 +161,19 @@
*/
private final @Nullable InlineSuggestionsRequest mInlineSuggestionsRequest;
+ /**
+ * Gets the {@link IntentSender} to send a delayed fill response.
+ *
+ * <p>The autofill service must first indicate that it wants to return a delayed
+ * {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful
+ * fill response. Then it can use this IntentSender to send an Intent with extra
+ * {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p>
+ *
+ * <p>Note that this may be null if a delayed fill response is not supported for
+ * this fill request.</p>
+ */
+ private final @Nullable IntentSender mDelayedFillIntentSender;
+
private void onConstructed() {
Preconditions.checkCollectionElementsNotNull(mFillContexts, "contexts");
}
@@ -252,6 +266,16 @@
*
* <p>The Autofill Service must set supportsInlineSuggestions in its XML to enable support
* for inline suggestions.</p>
+ * @param delayedFillIntentSender
+ * Gets the {@link IntentSender} to send a delayed fill response.
+ *
+ * <p>The autofill service must first indicate that it wants to return a delayed
+ * {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful
+ * fill response. Then it can use this IntentSender to send an Intent with extra
+ * {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p>
+ *
+ * <p>Note that this may be null if a delayed fill response is not supported for
+ * this fill request.</p>
* @hide
*/
@DataClass.Generated.Member
@@ -260,7 +284,8 @@
@NonNull List<FillContext> fillContexts,
@Nullable Bundle clientState,
@RequestFlags int flags,
- @Nullable InlineSuggestionsRequest inlineSuggestionsRequest) {
+ @Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
+ @Nullable IntentSender delayedFillIntentSender) {
this.mId = id;
this.mFillContexts = fillContexts;
com.android.internal.util.AnnotationValidations.validate(
@@ -276,6 +301,7 @@
| FLAG_VIEW_NOT_FOCUSED
| FLAG_ACTIVITY_START);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+ this.mDelayedFillIntentSender = delayedFillIntentSender;
onConstructed();
}
@@ -348,6 +374,22 @@
return mInlineSuggestionsRequest;
}
+ /**
+ * Gets the {@link IntentSender} to send a delayed fill response.
+ *
+ * <p>The autofill service must first indicate that it wants to return a delayed
+ * {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful
+ * fill response. Then it can use this IntentSender to send an Intent with extra
+ * {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p>
+ *
+ * <p>Note that this may be null if a delayed fill response is not supported for
+ * this fill request.</p>
+ */
+ @DataClass.Generated.Member
+ public @Nullable IntentSender getDelayedFillIntentSender() {
+ return mDelayedFillIntentSender;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -359,7 +401,8 @@
"fillContexts = " + mFillContexts + ", " +
"clientState = " + mClientState + ", " +
"flags = " + requestFlagsToString(mFlags) + ", " +
- "inlineSuggestionsRequest = " + mInlineSuggestionsRequest +
+ "inlineSuggestionsRequest = " + mInlineSuggestionsRequest + ", " +
+ "delayedFillIntentSender = " + mDelayedFillIntentSender +
" }";
}
@@ -372,12 +415,14 @@
byte flg = 0;
if (mClientState != null) flg |= 0x4;
if (mInlineSuggestionsRequest != null) flg |= 0x10;
+ if (mDelayedFillIntentSender != null) flg |= 0x20;
dest.writeByte(flg);
dest.writeInt(mId);
dest.writeParcelableList(mFillContexts, flags);
if (mClientState != null) dest.writeBundle(mClientState);
dest.writeInt(mFlags);
if (mInlineSuggestionsRequest != null) dest.writeTypedObject(mInlineSuggestionsRequest, flags);
+ if (mDelayedFillIntentSender != null) dest.writeTypedObject(mDelayedFillIntentSender, flags);
}
@Override
@@ -398,6 +443,7 @@
Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
int flags = in.readInt();
InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
+ IntentSender delayedFillIntentSender = (flg & 0x20) == 0 ? null : (IntentSender) in.readTypedObject(IntentSender.CREATOR);
this.mId = id;
this.mFillContexts = fillContexts;
@@ -414,6 +460,7 @@
| FLAG_VIEW_NOT_FOCUSED
| FLAG_ACTIVITY_START);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+ this.mDelayedFillIntentSender = delayedFillIntentSender;
onConstructed();
}
@@ -433,10 +480,10 @@
};
@DataClass.Generated(
- time = 1643052544776L,
+ time = 1643386870464L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
- inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_ACTIVITY_START\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_ACTIVITY_START\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 903e77f..296877a 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -65,10 +65,22 @@
*/
public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2;
+ /**
+ * Flag used to request to wait for a delayed fill from the remote Autofill service if it's
+ * passed to {@link Builder#setFlags(int)}.
+ *
+ * <p>Some datasets (i.e. OTP) take time to produce. This flags allows remote service to send
+ * a {@link FillResponse} to the latest {@link FillRequest} via
+ * {@link FillRequest#getDelayedFillIntentSender()} even if the original {@link FillCallback}
+ * has timed out.
+ */
+ public static final int FLAG_DELAY_FILL = 0x4;
+
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
FLAG_TRACK_CONTEXT_COMMITED,
- FLAG_DISABLE_ACTIVITY_ONLY
+ FLAG_DISABLE_ACTIVITY_ONLY,
+ FLAG_DELAY_FILL
})
@Retention(RetentionPolicy.SOURCE)
@interface FillResponseFlags {}
@@ -657,7 +669,7 @@
public Builder setFlags(@FillResponseFlags int flags) {
throwIfDestroyed();
mFlags = Preconditions.checkFlagsArgument(flags,
- FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
+ FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY | FLAG_DELAY_FILL);
return this;
}
diff --git a/core/java/android/service/games/GameService.java b/core/java/android/service/games/GameService.java
index 870a7e3..9df8358 100644
--- a/core/java/android/service/games/GameService.java
+++ b/core/java/android/service/games/GameService.java
@@ -16,9 +16,11 @@
package android.service.games;
+import android.Manifest;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.IGameManagerService;
@@ -173,6 +175,7 @@
*
* @param taskId The taskId of the game.
*/
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY)
public final void createGameSession(@IntRange(from = 0) int taskId) {
if (mGameServiceController == null) {
throw new IllegalStateException("Can not call before connected()");
diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java
index f4baedc..e33f180 100644
--- a/core/java/android/service/games/GameSession.java
+++ b/core/java/android/service/games/GameSession.java
@@ -84,6 +84,15 @@
}
@Override
+ public void onTransientSystemBarVisibilityFromRevealGestureChanged(
+ boolean visibleDueToGesture) {
+ Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
+ GameSession::dispatchTransientSystemBarVisibilityFromRevealGestureChanged,
+ GameSession.this,
+ visibleDueToGesture));
+ }
+
+ @Override
public void onTaskFocusChanged(boolean focused) {
Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
GameSession::moveToState, GameSession.this,
@@ -109,6 +118,7 @@
}
private LifecycleState mLifecycleState = LifecycleState.INITIALIZED;
+ private boolean mAreTransientInsetsVisibleDueToGesture = false;
private IGameSessionController mGameSessionController;
private int mTaskId;
private GameSessionRootView mGameSessionRootView;
@@ -138,11 +148,23 @@
}
@Hide
- void doDestroy() {
+ private void doDestroy() {
mSurfaceControlViewHost.release();
moveToState(LifecycleState.DESTROYED);
}
+ /** @hide */
+ @VisibleForTesting
+ @MainThread
+ public void dispatchTransientSystemBarVisibilityFromRevealGestureChanged(
+ boolean visibleDueToGesture) {
+ boolean didValueChange = mAreTransientInsetsVisibleDueToGesture != visibleDueToGesture;
+ mAreTransientInsetsVisibleDueToGesture = visibleDueToGesture;
+ if (didValueChange) {
+ onTransientSystemBarVisibilityFromRevealGestureChanged(visibleDueToGesture);
+ }
+ }
+
/**
* @hide
*/
@@ -252,7 +274,23 @@
*
* @param focused True if the game task is focused, false if the game task is unfocused.
*/
- public void onGameTaskFocusChanged(boolean focused) {}
+ public void onGameTaskFocusChanged(boolean focused) {
+ }
+
+ /**
+ * Called when the visibility of the transient system bars changed due to the user performing
+ * the reveal gesture. The reveal gesture is defined as a swipe to reveal the transient system
+ * bars that originates from the system bars.
+ *
+ * @param visibleDueToGesture if the transient bars triggered by the reveal gesture are visible.
+ * This is {@code true} when the transient system bars become visible
+ * due to user performing the reveal gesture. This is {@code false}
+ * when the transient system bars are hidden or become permanently
+ * visible.
+ */
+ public void onTransientSystemBarVisibilityFromRevealGestureChanged(
+ boolean visibleDueToGesture) {
+ }
/**
* Sets the task overlay content to an explicit view. This view is placed directly into the game
@@ -278,7 +316,7 @@
*
* @return {@code true} if the game was successfully restarted; otherwise, {@code false}.
*/
- @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+ @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY)
public final boolean restartGame() {
try {
mGameSessionController.restartGame(mTaskId);
@@ -344,12 +382,14 @@
/**
* Called when taking the screenshot failed.
+ *
* @param statusCode Indicates the reason for failure.
*/
void onFailure(@ScreenshotFailureStatus int statusCode);
/**
* Called when taking the screenshot succeeded.
+ *
* @param bitmap The screenshot.
*/
void onSuccess(@NonNull Bitmap bitmap);
diff --git a/core/java/android/service/games/IGameSession.aidl b/core/java/android/service/games/IGameSession.aidl
index 71da630..49c36c6 100644
--- a/core/java/android/service/games/IGameSession.aidl
+++ b/core/java/android/service/games/IGameSession.aidl
@@ -21,5 +21,6 @@
*/
oneway interface IGameSession {
void onDestroyed();
+ void onTransientSystemBarVisibilityFromRevealGestureChanged(boolean visibleDueToGesture);
void onTaskFocusChanged(boolean focused);
}
diff --git a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
index 220e498..6b11e74 100644
--- a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
+++ b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
@@ -26,6 +26,7 @@
oneway interface ITrustAgentServiceCallback {
void grantTrust(CharSequence message, long durationMs, int flags);
void revokeTrust();
+ void lockUser();
void setManagingTrust(boolean managingTrust);
void onConfigureCompleted(boolean result, IBinder token);
void addEscrowToken(in byte[] token, int userId);
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index fba61cf..8f6e1e0 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -119,16 +119,15 @@
* automatically remove trust after some conditions are met (detailed below) with the option for
* the agent to renew the trust again later.
*
- * <p>After this is called, the agent will grant trust until the platform thinks an active user
- * is no longer using that trust. For example, if the user dismisses keyguard, the platform will
- * remove trust (this does not automatically lock the device).
+ * <p>After this is called, the agent will grant trust until the platform thinks an active
+ * user is no longer using that trust. This can happen for any reason as determined by the
+ * platform. For example, if the user dismisses keyguard, the platform will remove trust;
+ * since this does not automatically lock the device, this results in the device locking the
+ * next time the screen turns off.
*
* <p>When the platform internally removes the agent's trust in this manner, an agent can
* re-grant it (via a call to grantTrust) without the user having to unlock the device through
* another method (e.g. PIN). This renewable state only persists for a limited time.
- *
- * TODO(b/213631675): Remove @hide
- * @hide
*/
public static final int FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE = 1 << 2;
@@ -139,9 +138,6 @@
* Without this flag, the message passed to {@code grantTrust} is only used for debugging
* purposes. With the flag, it may be displayed to the user as the reason why the device is
* unlocked.
- *
- * TODO(b/213911325): Remove @hide
- * @hide
*/
public static final int FLAG_GRANT_TRUST_DISPLAY_MESSAGE = 1 << 3;
@@ -309,9 +305,6 @@
* {@link #grantTrust(CharSequence, long, int)}.
*
* @see #FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE
- *
- * TODO(b/213631672): Add CTS tests
- * @hide
*/
public void onUserRequestedUnlock() {
}
@@ -624,11 +617,15 @@
*
* If the user has no auth method specified, then keyguard will still be shown but can be
* dismissed normally.
- *
- * TODO(b/213631675): Implement & make public
- * @hide
*/
public final void lockUser() {
+ if (mCallback != null) {
+ try {
+ mCallback.lockUser();
+ } catch (RemoteException e) {
+ onError("calling lockUser");
+ }
+ }
}
/**
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index f2a0355..c91851a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -905,11 +905,12 @@
if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) {
return;
}
+
+ SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction();
// TODO: apply the dimming to preview as well once surface transparency works in
// preview mode.
if (!isPreview() && mShouldDim) {
Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount);
- SurfaceControl.Transaction surfaceControl = new SurfaceControl.Transaction();
// Animate dimming to gradually change the wallpaper alpha from the previous
// dim amount to the new amount only if the dim amount changed.
@@ -919,16 +920,15 @@
? 0 : DIMMING_ANIMATION_DURATION_MS);
animator.addUpdateListener((ValueAnimator va) -> {
final float dimValue = (float) va.getAnimatedValue();
- surfaceControl
- .setAlpha(mBbqSurfaceControl, 1 - dimValue)
- .apply();
+ if (mBbqSurfaceControl != null) {
+ surfaceControlTransaction
+ .setAlpha(mBbqSurfaceControl, 1 - dimValue).apply();
+ }
});
animator.start();
} else {
Log.v(TAG, "Setting wallpaper dimming: " + 0);
- new SurfaceControl.Transaction()
- .setAlpha(mBbqSurfaceControl, 1.0f)
- .apply();
+ surfaceControlTransaction.setAlpha(mBbqSurfaceControl, 1.0f).apply();
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/service/wallpapereffectsgeneration/IWallpaperEffectsGenerationService.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/service/wallpapereffectsgeneration/IWallpaperEffectsGenerationService.aidl
index 861a4ed..ca75d2e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/service/wallpapereffectsgeneration/IWallpaperEffectsGenerationService.aidl
@@ -1,3 +1,4 @@
+
/*
* Copyright (C) 2022 The Android Open Source Project
*
@@ -14,6 +15,14 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package android.service.wallpapereffectsgeneration;
-parcelable DeviceInfo;
+import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
+/**
+ * Interface from the system to WallpaperEffectsGeneration service.
+ *
+ * @hide
+ */
+oneway interface IWallpaperEffectsGenerationService {
+ void onGenerateCinematicEffect(in CinematicEffectRequest request);
+}
\ No newline at end of file
diff --git a/core/java/android/service/wallpapereffectsgeneration/WallpaperEffectsGenerationService.java b/core/java/android/service/wallpapereffectsgeneration/WallpaperEffectsGenerationService.java
new file mode 100644
index 0000000..18b654e
--- /dev/null
+++ b/core/java/android/service/wallpapereffectsgeneration/WallpaperEffectsGenerationService.java
@@ -0,0 +1,142 @@
+/*
+ * 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 android.service.wallpapereffectsgeneration;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+import android.app.wallpapereffectsgeneration.IWallpaperEffectsGenerationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * A service for handling wallpaper effects generation tasks. It must implement
+ * (onGenerateCinematicEffect} method to generate response and call returnCinematicEffectResponse
+ * to send the response.
+ *
+ * <p>To extend this service, you must declare the service in your manifest file with the
+ * {@link android.Manifest.permission#BIND_WALLPAPER_EFFECTS_GENERATION} permission and includes
+ * an intent filter with the {@link #SERVICE_INTERFACE} action. For example: </p>
+ * <pre>
+ * <application>
+ * <service android:name=".CtsWallpaperEffectsGenerationService"
+ * android:exported="true"
+ * android:label="CtsWallpaperEffectsGenerationService"
+ * android:permission="android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService"
+ />
+ * </intent-filter>
+ * </service>
+ * <uses-library android:name="android.test.runner"/>
+ * </application>
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class WallpaperEffectsGenerationService extends Service {
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ *
+ * <p>The service must also require the
+ * {@link android.permission#MANAGE_WALLPAPER_EFFECTS_GENERATION}
+ * permission.
+ *
+ * @hide
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService";
+ private static final boolean DEBUG = false;
+ private static final String TAG = "WallpaperEffectsGenerationService";
+ private Handler mHandler;
+ private IWallpaperEffectsGenerationManager mService;
+
+ private final IWallpaperEffectsGenerationService mInterface =
+ new IWallpaperEffectsGenerationService.Stub() {
+ @Override
+ public void onGenerateCinematicEffect(CinematicEffectRequest request) {
+ mHandler.sendMessage(
+ obtainMessage(
+ WallpaperEffectsGenerationService::onGenerateCinematicEffect,
+ WallpaperEffectsGenerationService.this, request));
+ }
+ };
+
+ /**
+ * Called when the OS receives a request for generating cinematic effect. On receiving the
+ * request, it extract cinematic information from the input and call
+ * {@link #returnCinematicEffectResponse} with the textured mesh
+ * and metadata wrapped in CinematicEffectResponse.
+ *
+ * @param request the cinematic effect request passed from the client.
+ */
+ public abstract void onGenerateCinematicEffect(@NonNull CinematicEffectRequest request);
+
+ /**
+ * Returns the cinematic effect response. Must be called when cinematic effect
+ * response is generated and ready to be sent back. Otherwise the response won't be
+ * returned.
+ *
+ * @param response the cinematic effect response returned from service provider.
+ */
+ public final void returnCinematicEffectResponse(@NonNull CinematicEffectResponse response) {
+ try {
+ mService.returnCinematicEffectResponse(response);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (DEBUG) {
+ Log.d(TAG, "onCreate WallpaperEffectsGenerationService");
+ }
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ IBinder b = ServiceManager.getService(Context.WALLPAPER_EFFECTS_GENERATION_SERVICE);
+ mService = IWallpaperEffectsGenerationManager.Stub.asInterface(b);
+ }
+
+ @NonNull
+ @Override
+ public final IBinder onBind(@NonNull Intent intent) {
+ if (DEBUG) {
+ Log.d(TAG, "onBind WallpaperEffectsGenerationService");
+ }
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ Slog.w(TAG,
+ "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+ return null;
+ }
+}
diff --git a/core/java/android/util/IconDrawableFactory.java b/core/java/android/util/IconDrawableFactory.java
index b5e8dd7..5bb263a 100644
--- a/core/java/android/util/IconDrawableFactory.java
+++ b/core/java/android/util/IconDrawableFactory.java
@@ -15,7 +15,12 @@
*/
package android.util;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawables.UNDEFINED;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON_BADGE;
+
import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -37,6 +42,7 @@
protected final Context mContext;
protected final PackageManager mPm;
protected final UserManager mUm;
+ protected final DevicePolicyManager mDpm;
protected final LauncherIcons mLauncherIcons;
protected final boolean mEmbedShadow;
@@ -44,6 +50,7 @@
mContext = context;
mPm = context.getPackageManager();
mUm = context.getSystemService(UserManager.class);
+ mDpm = context.getSystemService(DevicePolicyManager.class);
mLauncherIcons = new LauncherIcons(context);
mEmbedShadow = embedShadow;
}
@@ -73,18 +80,32 @@
if (appInfo.isInstantApp()) {
int badgeColor = Resources.getSystem().getColor(
com.android.internal.R.color.instant_app_badge, null);
+ Drawable badge = mContext.getDrawable(
+ com.android.internal.R.drawable.ic_instant_icon_badge_bolt);
icon = mLauncherIcons.getBadgedDrawable(icon,
- com.android.internal.R.drawable.ic_instant_icon_badge_bolt,
+ badge,
badgeColor);
}
if (mUm.hasBadge(userId)) {
- icon = mLauncherIcons.getBadgedDrawable(icon,
- mUm.getUserIconBadgeResId(userId),
- mUm.getUserBadgeColor(userId));
+
+ Drawable badge = mDpm.getDrawable(
+ getUpdatableUserIconBadgeId(userId),
+ SOLID_COLORED,
+ () -> getDefaultUserIconBadge(userId));
+
+ icon = mLauncherIcons.getBadgedDrawable(icon, badge, mUm.getUserBadgeColor(userId));
}
return icon;
}
+ private String getUpdatableUserIconBadgeId(int userId) {
+ return mUm.isManagedProfile(userId) ? WORK_PROFILE_ICON_BADGE : UNDEFINED;
+ }
+
+ private Drawable getDefaultUserIconBadge(int userId) {
+ return mContext.getResources().getDrawable(mUm.getUserIconBadgeResId(userId));
+ }
+
/**
* Add shadow to the icon if {@link AdaptiveIconDrawable}
*/
diff --git a/core/java/android/util/LauncherIcons.java b/core/java/android/util/LauncherIcons.java
index e652e17..355b2e9 100644
--- a/core/java/android/util/LauncherIcons.java
+++ b/core/java/android/util/LauncherIcons.java
@@ -45,10 +45,12 @@
private final SparseArray<Bitmap> mShadowCache = new SparseArray<>();
private final int mIconSize;
private final Resources mRes;
+ private final Context mContext;
public LauncherIcons(Context context) {
mRes = context.getResources();
mIconSize = mRes.getDimensionPixelSize(android.R.dimen.app_icon_size);
+ mContext = context;
}
public Drawable wrapIconDrawableWithShadow(Drawable drawable) {
@@ -98,14 +100,14 @@
return shadow;
}
- public Drawable getBadgeDrawable(int foregroundRes, int backgroundColor) {
- return getBadgedDrawable(null, foregroundRes, backgroundColor);
+ public Drawable getBadgeDrawable(Drawable badgeForeground, int backgroundColor) {
+ return getBadgedDrawable(null, badgeForeground, backgroundColor);
}
- public Drawable getBadgedDrawable(Drawable base, int foregroundRes, int backgroundColor) {
+ public Drawable getBadgedDrawable(
+ Drawable base, Drawable badgeForeground, int backgroundColor) {
Resources overlayableRes =
ActivityThread.currentActivityThread().getApplication().getResources();
-
// ic_corp_icon_badge_shadow is not work-profile-specific.
Drawable badgeShadow = overlayableRes.getDrawable(
com.android.internal.R.drawable.ic_corp_icon_badge_shadow);
@@ -115,7 +117,6 @@
com.android.internal.R.drawable.ic_corp_icon_badge_color)
.getConstantState().newDrawable().mutate();
- Drawable badgeForeground = overlayableRes.getDrawable(foregroundRes);
badgeForeground.setTint(backgroundColor);
Drawable[] drawables = base == null
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index fa39380..246a8c9 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1118,6 +1118,19 @@
}
/**
+ * 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/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl
index 449e9b3..67ae743 100644
--- a/core/java/android/view/IDisplayWindowListener.aidl
+++ b/core/java/android/view/IDisplayWindowListener.aidl
@@ -63,5 +63,5 @@
/**
* Called when the keep clear ares on a display have changed.
*/
- void onKeepClearAreasChanged(int displayId, in List<Rect> keepClearAreas);
+ void onKeepClearAreasChanged(int displayId, in List<Rect> restricted, in List<Rect> unrestricted);
}
diff --git a/core/java/android/view/InputMonitor.java b/core/java/android/view/InputMonitor.java
index ad1f201..8801fe0 100644
--- a/core/java/android/view/InputMonitor.java
+++ b/core/java/android/view/InputMonitor.java
@@ -79,13 +79,17 @@
- // Code below generated by codegen v1.0.7.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/InputMonitor.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@DataClass.Generated.Member
@@ -126,7 +130,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -141,7 +145,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- /* package-private */ InputMonitor(Parcel in) {
+ /* package-private */ InputMonitor(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -167,17 +171,21 @@
}
@Override
- public InputMonitor createFromParcel(Parcel in) {
+ public InputMonitor createFromParcel(@NonNull Parcel in) {
return new InputMonitor(in);
}
};
@DataClass.Generated(
- time = 1571177265149L,
- codegenVersion = "1.0.7",
+ time = 1637697281750L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/InputMonitor.java",
inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\npublic void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
@Deprecated
private void __metadata() {}
+
+ //@formatter:on
+ // End of generated code
+
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 904d7c8..6f5fea2 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -197,6 +197,9 @@
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 boolean autoLowLatencyModeSupported;
public boolean gameContentTypeSupported;
+ public int preferredBootDisplayMode;
+
@Override
public String toString() {
return "DynamicDisplayInfo{"
@@ -1887,7 +1892,8 @@
+ ", activeColorMode=" + activeColorMode
+ ", hdrCapabilities=" + hdrCapabilities
+ ", autoLowLatencyModeSupported=" + autoLowLatencyModeSupported
- + ", gameContentTypeSupported" + gameContentTypeSupported + "}";
+ + ", gameContentTypeSupported" + gameContentTypeSupported
+ + ", preferredBootDisplayMode" + preferredBootDisplayMode + "}";
}
@Override
@@ -1899,7 +1905,8 @@
&& 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 @@
/**
* @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/android/view/Window.java b/core/java/android/view/Window.java
index 039b50a..8401b7c 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -295,6 +295,9 @@
private OnWindowDismissedCallback mOnWindowDismissedCallback;
private OnWindowSwipeDismissedCallback mOnWindowSwipeDismissedCallback;
private WindowControllerCallback mWindowControllerCallback;
+ @WindowInsetsController.Appearance
+ private int mSystemBarAppearance;
+ private DecorCallback mDecorCallback;
private OnRestrictedCaptionAreaChangedListener mOnRestrictedCaptionAreaChangedListener;
private Rect mRestrictedCaptionAreaRect;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -607,17 +610,6 @@
* @param hasCapture True if the window has pointer capture.
*/
default public void onPointerCaptureChanged(boolean hasCapture) { };
-
- /**
- * Called from
- * {@link com.android.internal.policy.DecorView#onSystemBarAppearanceChanged(int)}.
- *
- * @param appearance The newly applied appearance.
- * @hide
- */
- default void onSystemBarAppearanceChanged(
- @WindowInsetsController.Appearance int appearance) {
- }
}
/** @hide */
@@ -672,6 +664,35 @@
void updateNavigationBarColor(int color);
}
+ /** @hide */
+ public interface DecorCallback {
+ /**
+ * Called from
+ * {@link com.android.internal.policy.DecorView#onSystemBarAppearanceChanged(int)}.
+ *
+ * @param appearance The newly applied appearance.
+ */
+ void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance);
+
+ /**
+ * Called from
+ * {@link com.android.internal.policy.DecorView#updateColorViews(WindowInsets, boolean)}
+ * when {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackground} is
+ * being updated.
+ *
+ * @param drawLegacyNavigationBarBackground the new value that is being set to
+ * {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackground}.
+ * @return The value to be set to
+ * {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackgroundHandled}
+ * on behalf of the {@link com.android.internal.policy.DecorView}.
+ * {@code true} to tell that the Window can render the legacy navigation bar
+ * background on behalf of the {@link com.android.internal.policy.DecorView}.
+ * {@code false} to let {@link com.android.internal.policy.DecorView} handle it.
+ */
+ boolean onDrawLegacyNavigationBarBackgroundChanged(
+ boolean drawLegacyNavigationBarBackground);
+ }
+
/**
* Callback for clients that want to be aware of where caption draws content.
*/
@@ -996,6 +1017,36 @@
return mWindowControllerCallback;
}
+ /** @hide */
+ public final void setDecorCallback(DecorCallback decorCallback) {
+ mDecorCallback = decorCallback;
+ }
+
+ /** @hide */
+ @WindowInsetsController.Appearance
+ public final int getSystemBarAppearance() {
+ return mSystemBarAppearance;
+ }
+
+ /** @hide */
+ public final void dispatchOnSystemBarAppearanceChanged(
+ @WindowInsetsController.Appearance int appearance) {
+ mSystemBarAppearance = appearance;
+ if (mDecorCallback != null) {
+ mDecorCallback.onSystemBarAppearanceChanged(appearance);
+ }
+ }
+
+ /** @hide */
+ public final boolean onDrawLegacyNavigationBarBackgroundChanged(
+ boolean drawLegacyNavigationBarBackground) {
+ if (mDecorCallback == null) {
+ return false;
+ }
+ return mDecorCallback.onDrawLegacyNavigationBarBackgroundChanged(
+ drawLegacyNavigationBarBackground);
+ }
+
/**
* Set a callback for changes of area where caption will draw its content.
*
diff --git a/core/java/android/view/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java
index 115e9e8..02c8945 100644
--- a/core/java/android/view/WindowCallbackWrapper.java
+++ b/core/java/android/view/WindowCallbackWrapper.java
@@ -163,10 +163,5 @@
public void onPointerCaptureChanged(boolean hasCapture) {
mWrapped.onPointerCaptureChanged(hasCapture);
}
-
- @Override
- public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
- mWrapped.onSystemBarAppearanceChanged(appearance);
- }
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 0a33d6c..a31cacf 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -710,6 +710,8 @@
private static final int BOOLEAN_PROPERTY_IS_TEXT_ENTRY_KEY = 0x0400000;
+ private static final int BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE = 0x0800000;
+
/**
* Bits that provide the id of a virtual descendant of a view.
*/
@@ -2276,6 +2278,38 @@
}
/**
+ * Gets if the node has selectable text.
+ *
+ * <p>
+ * Services should use {@link #ACTION_SET_SELECTION} for selection. Editable text nodes must
+ * also be selectable. But not all UIs will populate this field, so services should consider
+ * 'isTextSelectable | isEditable' to ensure they don't miss nodes with selectable text.
+ * </p>
+ *
+ * @see #isEditable
+ * @return True if the node has selectable text.
+ */
+ public boolean isTextSelectable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE);
+ }
+
+ /**
+ * Sets if the node has selectable text.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param selectableText True if the node has selectable text, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setTextSelectable(boolean selectableText) {
+ setBooleanProperty(BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE, selectableText);
+ }
+
+ /**
* Gets if the node is editable.
*
* @return True if the node is editable, false otherwise.
@@ -4327,8 +4361,12 @@
return "ACTION_CANCEL_DRAG";
case R.id.accessibilityActionDragDrop:
return "ACTION_DROP";
- default:
+ default: {
+ if (action == R.id.accessibilityActionShowSuggestions) {
+ return "ACTION_SHOW_SUGGESTIONS";
+ }
return "ACTION_UNKNOWN";
+ }
}
}
@@ -4462,6 +4500,7 @@
builder.append("; importantForAccessibility: ").append(isImportantForAccessibility());
builder.append("; visible: ").append(isVisibleToUser());
builder.append("; actions: ").append(mActions);
+ builder.append("; isTextSelectable: ").append(isTextSelectable());
return builder.toString();
}
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index ab749ee..3914a3c 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -865,6 +865,15 @@
}
/**
+ * @return if a window animation has outsets applied to it.
+ *
+ * @hide
+ */
+ public boolean hasExtension() {
+ return false;
+ }
+
+ /**
* If showBackground is {@code true} and this animation is applied on a window, then the windows
* in the animation will animate with the background associated with this window behind them.
*
@@ -942,6 +951,21 @@
}
/**
+ * Gets the transformation to apply a specific point in time. Implementations of this method
+ * should always be kept in sync with getTransformation.
+ *
+ * @param normalizedTime time between 0 and 1 where 0 is the start of the animation and 1 the
+ * end.
+ * @param outTransformation A transformation object that is provided by the
+ * caller and will be filled in by the animation.
+ * @hide
+ */
+ public void getTransformationAt(float normalizedTime, Transformation outTransformation) {
+ final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
+ applyTransformation(interpolatedTime, outTransformation);
+ }
+
+ /**
* Gets the transformation to apply at a specified point in time. Implementations of this
* method should always replace the specified Transformation or document they are doing
* otherwise.
@@ -987,8 +1011,7 @@
normalizedTime = 1.0f - normalizedTime;
}
- final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
- applyTransformation(interpolatedTime, outTransformation);
+ getTransformationAt(normalizedTime, outTransformation);
}
if (expired) {
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 03c6ca6..a2f3544 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -363,6 +363,26 @@
* The transformation of an animation set is the concatenation of all of its
* component animations.
*
+ * @see android.view.animation.Animation#getTransformationAt
+ * @hide
+ */
+ @Override
+ public void getTransformationAt(float interpolatedTime, Transformation t) {
+ final Transformation temp = mTempTransformation;
+
+ for (int i = mAnimations.size() - 1; i >= 0; --i) {
+ final Animation a = mAnimations.get(i);
+
+ temp.clear();
+ a.getTransformationAt(interpolatedTime, t);
+ t.compose(temp);
+ }
+ }
+
+ /**
+ * The transformation of an animation set is the concatenation of all of its
+ * component animations.
+ *
* @see android.view.animation.Animation#getTransformation
*/
@Override
@@ -517,4 +537,15 @@
public boolean willChangeBounds() {
return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK;
}
+
+ /** @hide */
+ @Override
+ public boolean hasExtension() {
+ for (Animation animation : mAnimations) {
+ if (animation.hasExtension()) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 7ce0f45..7d1dc76 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -190,6 +190,8 @@
anim = new TranslateAnimation(c, attrs);
} else if (name.equals("cliprect")) {
anim = new ClipRectAnimation(c, attrs);
+ } else if (name.equals("extend")) {
+ anim = new ExtendAnimation(c, attrs);
} else {
throw new RuntimeException("Unknown animation name: " + parser.getName());
}
diff --git a/core/java/android/view/animation/ExtendAnimation.java b/core/java/android/view/animation/ExtendAnimation.java
new file mode 100644
index 0000000..fd627e5
--- /dev/null
+++ b/core/java/android/view/animation/ExtendAnimation.java
@@ -0,0 +1,176 @@
+/*
+ * 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 android.view.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Insets;
+import android.util.AttributeSet;
+
+/**
+ * An animation that controls the outset of an object.
+ *
+ * @hide
+ */
+public class ExtendAnimation extends Animation {
+ protected Insets mFromInsets = Insets.NONE;
+ protected Insets mToInsets = Insets.NONE;
+
+ private int mFromLeftType = ABSOLUTE;
+ private int mFromTopType = ABSOLUTE;
+ private int mFromRightType = ABSOLUTE;
+ private int mFromBottomType = ABSOLUTE;
+
+ private int mToLeftType = ABSOLUTE;
+ private int mToTopType = ABSOLUTE;
+ private int mToRightType = ABSOLUTE;
+ private int mToBottomType = ABSOLUTE;
+
+ private float mFromLeftValue;
+ private float mFromTopValue;
+ private float mFromRightValue;
+ private float mFromBottomValue;
+
+ private float mToLeftValue;
+ private float mToTopValue;
+ private float mToRightValue;
+ private float mToBottomValue;
+
+ /**
+ * Constructor used when an ExtendAnimation is loaded from a resource.
+ *
+ * @param context Application context to use
+ * @param attrs Attribute set from which to read values
+ */
+ public ExtendAnimation(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.ExtendAnimation);
+
+ Description d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_fromExtendLeft));
+ mFromLeftType = d.type;
+ mFromLeftValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_fromExtendTop));
+ mFromTopType = d.type;
+ mFromTopValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_fromExtendRight));
+ mFromRightType = d.type;
+ mFromRightValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_fromExtendBottom));
+ mFromBottomType = d.type;
+ mFromBottomValue = d.value;
+
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_toExtendLeft));
+ mToLeftType = d.type;
+ mToLeftValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_toExtendTop));
+ mToTopType = d.type;
+ mToTopValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_toExtendRight));
+ mToRightType = d.type;
+ mToRightValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_toExtendBottom));
+ mToBottomType = d.type;
+ mToBottomValue = d.value;
+
+ a.recycle();
+ }
+
+ /**
+ * Constructor to use when building an ExtendAnimation from code
+ *
+ * @param fromInsets the insets to animate from
+ * @param toInsets the insets to animate to
+ */
+ public ExtendAnimation(Insets fromInsets, Insets toInsets) {
+ if (fromInsets == null || toInsets == null) {
+ throw new RuntimeException("Expected non-null animation outsets");
+ }
+ mFromLeftValue = -fromInsets.left;
+ mFromTopValue = -fromInsets.top;
+ mFromRightValue = -fromInsets.right;
+ mFromBottomValue = -fromInsets.bottom;
+
+ mToLeftValue = -toInsets.left;
+ mToTopValue = -toInsets.top;
+ mToRightValue = -toInsets.right;
+ mToBottomValue = -toInsets.bottom;
+ }
+
+ /**
+ * Constructor to use when building an ExtendAnimation from code
+ */
+ public ExtendAnimation(int fromL, int fromT, int fromR, int fromB,
+ int toL, int toT, int toR, int toB) {
+ this(Insets.of(-fromL, -fromT, -fromR, -fromB), Insets.of(-toL, -toT, -toR, -toB));
+ }
+
+ @Override
+ protected void applyTransformation(float it, Transformation tr) {
+ int l = mFromInsets.left + (int) ((mToInsets.left - mFromInsets.left) * it);
+ int t = mFromInsets.top + (int) ((mToInsets.top - mFromInsets.top) * it);
+ int r = mFromInsets.right + (int) ((mToInsets.right - mFromInsets.right) * it);
+ int b = mFromInsets.bottom + (int) ((mToInsets.bottom - mFromInsets.bottom) * it);
+ tr.setInsets(l, t, r, b);
+ }
+
+ @Override
+ public boolean willChangeTransformationMatrix() {
+ return false;
+ }
+
+ /** @hide */
+ @Override
+ public boolean hasExtension() {
+ return mFromInsets.left < 0 || mFromInsets.top < 0 || mFromInsets.right < 0
+ || mFromInsets.bottom < 0;
+ }
+
+ @Override
+ public void initialize(int width, int height, int parentWidth, int parentHeight) {
+ super.initialize(width, height, parentWidth, parentHeight);
+ // We remove any negative extension (i.e. positive insets) and set those to 0
+ mFromInsets = Insets.min(Insets.of(
+ -(int) resolveSize(mFromLeftType, mFromLeftValue, width, parentWidth),
+ -(int) resolveSize(mFromTopType, mFromTopValue, height, parentHeight),
+ -(int) resolveSize(mFromRightType, mFromRightValue, width, parentWidth),
+ -(int) resolveSize(mFromBottomType, mFromBottomValue, height, parentHeight)
+ ), Insets.NONE);
+ mToInsets = Insets.min(Insets.of(
+ -(int) resolveSize(mToLeftType, mToLeftValue, width, parentWidth),
+ -(int) resolveSize(mToTopType, mToTopValue, height, parentHeight),
+ -(int) resolveSize(mToRightType, mToRightValue, width, parentWidth),
+ -(int) resolveSize(mToBottomType, mToBottomValue, height, parentHeight)
+ ), Insets.NONE);
+ }
+}
diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java
index b35a66e..bd62308 100644
--- a/core/java/android/view/animation/Transformation.java
+++ b/core/java/android/view/animation/Transformation.java
@@ -18,6 +18,7 @@
import android.annotation.FloatRange;
import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Rect;
@@ -53,6 +54,8 @@
private boolean mHasClipRect;
private Rect mClipRect = new Rect();
+ private Insets mInsets = Insets.NONE;
+
/**
* Creates a new transformation with alpha = 1 and the identity matrix.
*/
@@ -132,8 +135,9 @@
setClipRect(bounds);
}
}
+ setInsets(Insets.add(getInsets(), t.getInsets()));
}
-
+
/**
* Like {@link #compose(Transformation)} but does this.postConcat(t) of
* the transformation matrix.
@@ -160,7 +164,7 @@
public Matrix getMatrix() {
return mMatrix;
}
-
+
/**
* Sets the degree of transparency
* @param alpha 1.0 means fully opaqe and 0.0 means fully transparent
@@ -170,6 +174,13 @@
}
/**
+ * @return The degree of transparency
+ */
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ /**
* Sets the current Transform's clip rect
* @hide
*/
@@ -203,12 +214,29 @@
}
/**
- * @return The degree of transparency
+ * Sets the current Transform's insets
+ * @hide
*/
- public float getAlpha() {
- return mAlpha;
+ public void setInsets(Insets insets) {
+ mInsets = insets;
}
-
+
+ /**
+ * Sets the current Transform's insets
+ * @hide
+ */
+ public void setInsets(int left, int top, int right, int bottom) {
+ mInsets = Insets.of(left, top, right, bottom);
+ }
+
+ /**
+ * Returns the current Transform's outset rect
+ * @hide
+ */
+ public Insets getInsets() {
+ return mInsets;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder(64);
@@ -216,7 +244,7 @@
toShortString(sb);
return sb.toString();
}
-
+
/**
* Return a string representation of the transformation in a compact form.
*/
@@ -225,7 +253,7 @@
toShortString(sb);
return sb.toString();
}
-
+
/**
* @hide
*/
@@ -234,7 +262,7 @@
sb.append(" matrix="); sb.append(mMatrix.toShortString());
sb.append('}');
}
-
+
/**
* Print short string, to optimize dumping.
* @hide
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 9552691..2134d81 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -291,6 +291,8 @@
* <p>Typically used to change the context associated with the default session from an activity.
*/
public final void setContentCaptureContext(@Nullable ContentCaptureContext context) {
+ if (!isContentCaptureEnabled()) return;
+
mClientContext = context;
updateContentCaptureContext(context);
}
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index ff6903e8..08cc31c 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -105,12 +105,14 @@
* current IME.
* @param configChanges {@link InputMethodInfo#getConfigChanges()} declared by IME.
* @param stylusHwSupported {@link InputMethodInfo#supportsStylusHandwriting()} declared by IME.
+ * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be
+ * shown while the IME is shown.
* @hide
*/
@MainThread
default void initializeInternal(IBinder token,
IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
- boolean stylusHwSupported) {
+ boolean stylusHwSupported, boolean shouldShowImeSwitcherWhenImeIsShown) {
attachToken(token);
}
@@ -229,6 +231,8 @@
* the next {@link #startInput(InputConnection, EditorInfo, IBinder)} as
* long as your implementation of {@link InputMethod} relies on such
* IPCs
+ * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be
+ * shown while the IME is shown.
* @see #startInput(InputConnection, EditorInfo)
* @see #restartInput(InputConnection, EditorInfo)
* @see EditorInfo
@@ -237,7 +241,7 @@
@MainThread
default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
@NonNull EditorInfo editorInfo, boolean restarting,
- @NonNull IBinder startInputToken) {
+ @NonNull IBinder startInputToken, boolean shouldShowImeSwitcherWhenImeIsShown) {
if (restarting) {
restartInput(inputConnection, editorInfo);
} else {
@@ -246,6 +250,18 @@
}
/**
+ * Notifies that whether the IME should show the IME switcher or not is being changed.
+ *
+ * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be
+ * shown while the IME is shown.
+ * @hide
+ */
+ @MainThread
+ default void onShouldShowImeSwitcherWhenImeIsShownChanged(
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
+ }
+
+ /**
* Create a new {@link InputMethodSession} that can be handed to client
* applications for interacting with the input method. You can later
* use {@link #revokeSession(InputMethodSession)} to destroy the session
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index c6f64f4..b00a382 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -301,6 +301,13 @@
public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4;
/**
+ * This mask determines which flags are propagated to nested RemoteViews (either added by
+ * addView, or set as portrait/landscape/sized RemoteViews).
+ */
+ static final int FLAG_MASK_TO_PROPAGATE =
+ FLAG_WIDGET_IS_COLLECTION_CHILD | FLAG_USE_LIGHT_BACKGROUND_LAYOUT;
+
+ /**
* A ReadWriteHelper which has the same behavior as ReadWriteHelper.DEFAULT, but which is
* intentionally a different instance in order to trick Bundle reader so that it doesn't allow
* lazy initialization.
@@ -467,6 +474,18 @@
*/
public void addFlags(@ApplyFlags int flags) {
mApplyFlags = mApplyFlags | flags;
+
+ int flagsToPropagate = flags & FLAG_MASK_TO_PROPAGATE;
+ if (flagsToPropagate != 0) {
+ if (hasSizedRemoteViews()) {
+ for (RemoteViews remoteView : mSizedRemoteViews) {
+ remoteView.addFlags(flagsToPropagate);
+ }
+ } else if (hasLandscapeAndPortraitLayouts()) {
+ mLandscape.addFlags(flagsToPropagate);
+ mPortrait.addFlags(flagsToPropagate);
+ }
+ }
}
/**
@@ -2407,6 +2426,10 @@
// will return -1.
final int nextChild = getNextRecyclableChild(target);
RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context);
+
+ int flagsToPropagate = mApplyFlags & FLAG_MASK_TO_PROPAGATE;
+ if (flagsToPropagate != 0) rvToApply.addFlags(flagsToPropagate);
+
if (nextChild >= 0 && mStableId != NO_ID) {
// At that point, the views starting at index nextChild are the ones recyclable but
// not yet recycled. All views added on that round of application are placed before.
@@ -2419,8 +2442,8 @@
target.removeViews(nextChild, recycledViewIndex - nextChild);
}
setNextRecyclableChild(target, nextChild + 1, target.getChildCount());
- rvToApply.reapply(context, child, handler, null /* size */, colorResources,
- false /* topLevel */);
+ rvToApply.reapplyNestedViews(context, child, rootParent, handler,
+ null /* size */, colorResources);
return;
}
// If we cannot recycle the views, we still remove all views in between to
@@ -2431,8 +2454,8 @@
// If we cannot recycle, insert the new view before the next recyclable child.
// Inflate nested views and add as children
- View nestedView = rvToApply.apply(context, target, handler, null /* size */,
- colorResources);
+ View nestedView = rvToApply.applyNestedViews(context, target, rootParent, handler,
+ null /* size */, colorResources);
if (mStableId != NO_ID) {
setStableId(nestedView, mStableId);
}
@@ -3780,7 +3803,7 @@
* @param parcel
*/
public RemoteViews(Parcel parcel) {
- this(parcel, /* rootParent= */ null, /* info= */ null, /* depth= */ 0);
+ this(parcel, /* rootData= */ null, /* info= */ null, /* depth= */ 0);
}
private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData,
@@ -5580,6 +5603,16 @@
return result;
}
+ private View applyNestedViews(Context context, ViewGroup directParent,
+ ViewGroup rootParent, InteractionHandler handler, SizeF size,
+ ColorResources colorResources) {
+ RemoteViews rvToApply = getRemoteViewsToApply(context, size);
+
+ View result = inflateView(context, rvToApply, directParent, 0, colorResources);
+ rvToApply.performApply(result, rootParent, handler, colorResources);
+ return result;
+ }
+
private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
return inflateView(context, rv, parent, 0, null);
}
@@ -5895,6 +5928,12 @@
}
}
+ private void reapplyNestedViews(Context context, View v, ViewGroup rootParent,
+ InteractionHandler handler, SizeF size, ColorResources colorResources) {
+ RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size);
+ rvToApply.performApply(v, rootParent, handler, colorResources);
+ }
+
/**
* Applies all the actions to the provided view, moving as much of the task on the background
* thread as possible.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 41c5401..0fe2ed5 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4461,7 +4461,7 @@
* pixel" units. This size is adjusted based on the current density and
* user font size preference.
*
- * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
+ * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
*
* @param size The scaled pixel size.
*
@@ -4476,7 +4476,7 @@
* Set the default text size to a given unit and value. See {@link
* TypedValue} for the possible dimension units.
*
- * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
+ * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
*
* @param unit The desired dimension unit.
* @param size The desired size in the given units.
@@ -12289,6 +12289,7 @@
EXTRA_DATA_RENDERING_INFO_KEY,
EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
));
+ info.setTextSelectable(isTextSelectable());
} else {
info.setAvailableExtraData(Arrays.asList(
EXTRA_DATA_RENDERING_INFO_KEY
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 9648008..629a1b3 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -113,7 +113,7 @@
void notePhoneOn();
void notePhoneOff();
void notePhoneSignalStrength(in SignalStrength signalStrength);
- void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType);
+ void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType, int nrFrequency);
void notePhoneState(int phoneState);
void noteWifiOn();
void noteWifiOff();
@@ -145,6 +145,8 @@
long getAwakeTimeBattery();
long getAwakeTimePlugged();
+ void noteBluetoothOn(int uid, int reason, String packageName);
+ void noteBluetoothOff(int uid, int reason, String packageName);
void noteBleScanStarted(in WorkSource ws, boolean isUnoptimized);
void noteBleScanStopped(in WorkSource ws, boolean isUnoptimized);
void noteBleScanReset();
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 347153c..cdb69e5 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1011,7 +1011,9 @@
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 @@
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 @@
rebuildCompleted = rebuildCompleted && rebuildInactiveCompleted;
}
+ if (shouldUseMiniResolver()) {
+ configureMiniResolverContent();
+ return false;
+ }
+
if (useLayoutWithDefault()) {
mLayoutId = R.layout.resolver_list_with_default;
} else {
@@ -1578,6 +1587,72 @@
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/content/PackageHelper.java b/core/java/com/android/internal/content/InstallLocationUtils.java
similarity index 87%
rename from core/java/com/android/internal/content/PackageHelper.java
rename to core/java/com/android/internal/content/InstallLocationUtils.java
index c2f2052..c456cf3 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/InstallLocationUtils.java
@@ -16,6 +16,7 @@
package com.android.internal.content;
+import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL;
import android.content.Context;
@@ -53,7 +54,7 @@
* and media container service transports.
* Some utility methods to invoke StorageManagerService api.
*/
-public class PackageHelper {
+public class InstallLocationUtils {
public static final int RECOMMEND_INSTALL_INTERNAL = 1;
public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
public static final int RECOMMEND_INSTALL_EPHEMERAL = 3;
@@ -89,9 +90,13 @@
*/
public static abstract class TestableInterface {
abstract public StorageManager getStorageManager(Context context);
+
abstract public boolean getForceAllowOnExternalSetting(Context context);
+
abstract public boolean getAllow3rdPartyOnInternalConfig(Context context);
+
abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName);
+
abstract public File getDataDirectory();
}
@@ -150,11 +155,11 @@
/**
* Given a requested {@link PackageInfo#installLocation} and calculated
* install size, pick the actual volume to install the app. Only considers
- * internal and private volumes, and prefers to keep an existing package on
+ * internal and private volumes, and prefers to keep an existing package onocation
* its current volume.
*
* @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null}
- * for internal storage.
+ * for internal storage.
*/
public static String resolveInstallVolume(Context context, SessionParams params)
throws IOException {
@@ -316,21 +321,6 @@
&& params.sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
}
- @Deprecated
- public static int resolveInstallLocation(Context context, String packageName,
- int installLocation, long sizeBytes, int installFlags) {
- final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
- params.appPackageName = packageName;
- params.installLocation = installLocation;
- params.sizeBytes = sizeBytes;
- params.installFlags = installFlags;
- try {
- return resolveInstallLocation(context, params);
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
- }
-
/**
* Given a requested {@link PackageInfo#installLocation} and calculated
* install size, pick the actual location to install the app.
@@ -393,24 +383,24 @@
// and will fall through to return INSUFFICIENT_STORAGE
if (fitsOnInternal) {
return (ephemeral)
- ? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL
- : PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+ ? InstallLocationUtils.RECOMMEND_INSTALL_EPHEMERAL
+ : InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
}
} else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
if (fitsOnExternal) {
- return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
}
}
if (checkBoth) {
if (fitsOnInternal) {
- return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
} else if (fitsOnExternal) {
- return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
}
}
- return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+ return InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
}
@Deprecated
@@ -476,4 +466,48 @@
return 0;
}
}
+
+ public static int installLocationPolicy(int installLocation, int recommendedInstallLocation,
+ int installFlags, boolean installedPkgIsSystem, boolean installedPackageOnExternal) {
+ if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) {
+ // Invalid install. Return error code
+ return RECOMMEND_FAILED_ALREADY_EXISTS;
+ }
+ // Check for updated system application.
+ if (installedPkgIsSystem) {
+ return RECOMMEND_INSTALL_INTERNAL;
+ }
+ // If current upgrade specifies particular preference
+ if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+ // Application explicitly specified internal.
+ return RECOMMEND_INSTALL_INTERNAL;
+ } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
+ // App explicitly prefers external. Let policy decide
+ return recommendedInstallLocation;
+ } else {
+ // Prefer previous location
+ if (installedPackageOnExternal) {
+ return RECOMMEND_INSTALL_EXTERNAL;
+ }
+ return RECOMMEND_INSTALL_INTERNAL;
+ }
+ }
+
+ public static int getInstallationErrorCode(int loc) {
+ if (loc == RECOMMEND_FAILED_INVALID_LOCATION) {
+ return PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+ } else if (loc == RECOMMEND_FAILED_ALREADY_EXISTS) {
+ return PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
+ } else if (loc == RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
+ return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ } else if (loc == RECOMMEND_FAILED_INVALID_APK) {
+ return PackageManager.INSTALL_FAILED_INVALID_APK;
+ } else if (loc == RECOMMEND_FAILED_INVALID_URI) {
+ return PackageManager.INSTALL_FAILED_INVALID_URI;
+ } else if (loc == RECOMMEND_MEDIA_UNAVAILABLE) {
+ return PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
+ } else {
+ return INSTALL_SUCCEEDED;
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 25ee2d0..c0fec62 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -69,10 +69,14 @@
import android.os.connectivity.WifiActivityEnergyInfo;
import android.os.connectivity.WifiBatteryStats;
import android.provider.Settings;
+import android.telephony.Annotation.NetworkType;
import android.telephony.CellSignalStrength;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthNr;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
import android.telephony.ServiceState;
+import android.telephony.ServiceState.RegState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -935,6 +939,119 @@
final StopwatchTimer[] mPhoneDataConnectionsTimer =
new StopwatchTimer[NUM_DATA_CONNECTION_TYPES];
+ @RadioAccessTechnology
+ int mActiveRat = RADIO_ACCESS_TECHNOLOGY_OTHER;
+
+ private static class RadioAccessTechnologyBatteryStats {
+ /**
+ * This RAT is currently being used.
+ */
+ private boolean mActive = false;
+ /**
+ * Current active frequency range for this RAT.
+ */
+ @ServiceState.FrequencyRange
+ private int mFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
+ /**
+ * Current signal strength for this RAT.
+ */
+ private int mSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ /**
+ * Timers for each combination of frequency range and signal strength.
+ */
+ public final StopwatchTimer[][] perStateTimers;
+
+ RadioAccessTechnologyBatteryStats(int freqCount, Clock clock, TimeBase timeBase) {
+ perStateTimers =
+ new StopwatchTimer[freqCount][CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+ for (int i = 0; i < freqCount; i++) {
+ for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
+ perStateTimers[i][j] = new StopwatchTimer(clock, null, -1, null, timeBase);
+ }
+ }
+ }
+
+ /**
+ * Note this RAT is currently being used.
+ */
+ public void noteActive(boolean active, long elapsedRealtimeMs) {
+ if (mActive == active) return;
+ mActive = active;
+ if (mActive) {
+ perStateTimers[mFrequencyRange][mSignalStrength].startRunningLocked(
+ elapsedRealtimeMs);
+ } else {
+ perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(
+ elapsedRealtimeMs);
+ }
+ }
+
+ /**
+ * Note current frequency range has changed.
+ */
+ public void noteFrequencyRange(@ServiceState.FrequencyRange int frequencyRange,
+ long elapsedRealtimeMs) {
+ if (mFrequencyRange == frequencyRange) return;
+
+ if (!mActive) {
+ // RAT not in use, note the frequency change and move on.
+ mFrequencyRange = frequencyRange;
+ return;
+ }
+ perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs);
+ perStateTimers[frequencyRange][mSignalStrength].startRunningLocked(elapsedRealtimeMs);
+ mFrequencyRange = frequencyRange;
+ }
+
+ /**
+ * Note current signal strength has changed.
+ */
+ public void noteSignalStrength(int signalStrength, long elapsedRealtimeMs) {
+ if (mSignalStrength == signalStrength) return;
+
+ if (!mActive) {
+ // RAT not in use, note the signal strength change and move on.
+ mSignalStrength = signalStrength;
+ return;
+ }
+ perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs);
+ perStateTimers[mFrequencyRange][signalStrength].startRunningLocked(elapsedRealtimeMs);
+ mSignalStrength = signalStrength;
+ }
+
+ /**
+ * Reset display timers.
+ */
+ public void reset(long elapsedRealtimeUs) {
+ final int size = perStateTimers.length;
+ for (int i = 0; i < size; i++) {
+ for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
+ perStateTimers[i][j].reset(false, elapsedRealtimeUs);
+ }
+ }
+ }
+ }
+
+ /**
+ * Number of frequency ranges, keep in sync with {@link ServiceState.FrequencyRange}
+ */
+ private static final int NR_FREQUENCY_COUNT = 5;
+
+ RadioAccessTechnologyBatteryStats[] mPerRatBatteryStats =
+ new RadioAccessTechnologyBatteryStats[RADIO_ACCESS_TECHNOLOGY_COUNT];
+
+ @GuardedBy("this")
+ private RadioAccessTechnologyBatteryStats getRatBatteryStatsLocked(
+ @RadioAccessTechnology int rat) {
+ RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat];
+ if (stats == null) {
+ final int freqCount = rat == RADIO_ACCESS_TECHNOLOGY_NR ? NR_FREQUENCY_COUNT : 1;
+ stats = new RadioAccessTechnologyBatteryStats(freqCount, mClock, mOnBatteryTimeBase);
+ mPerRatBatteryStats[rat] = stats;
+ }
+ return stats;
+ }
+
final LongSamplingCounter[] mNetworkByteActivityCounters =
new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
@@ -1896,17 +2013,15 @@
private final TimeBase mTimeBase;
private final LongArrayMultiStateCounter mCounter;
- private TimeInFreqMultiStateCounter(TimeBase timeBase, Parcel in, long timestampMs) {
- mTimeBase = timeBase;
- mCounter = LongArrayMultiStateCounter.CREATOR.createFromParcel(in);
- mCounter.setEnabled(mTimeBase.isRunning(), timestampMs);
- timeBase.add(this);
- }
-
private TimeInFreqMultiStateCounter(TimeBase timeBase, int stateCount, int cpuFreqCount,
long timestampMs) {
+ this(timeBase, new LongArrayMultiStateCounter(stateCount, cpuFreqCount), timestampMs);
+ }
+
+ private TimeInFreqMultiStateCounter(TimeBase timeBase, LongArrayMultiStateCounter counter,
+ long timestampMs) {
mTimeBase = timeBase;
- mCounter = new LongArrayMultiStateCounter(stateCount, cpuFreqCount);
+ mCounter = counter;
mCounter.setEnabled(mTimeBase.isRunning(), timestampMs);
timeBase.add(this);
}
@@ -1915,6 +2030,19 @@
mCounter.writeToParcel(out, 0);
}
+ @Nullable
+ private static TimeInFreqMultiStateCounter readFromParcel(Parcel in, TimeBase timeBase,
+ int stateCount, int cpuFreqCount, long timestampMs) {
+ // Read the object from the Parcel, whether it's usable or not
+ LongArrayMultiStateCounter counter =
+ LongArrayMultiStateCounter.CREATOR.createFromParcel(in);
+ if (counter.getStateCount() != stateCount
+ || counter.getArrayLength() != cpuFreqCount) {
+ return null;
+ }
+ return new TimeInFreqMultiStateCounter(timeBase, counter, timestampMs);
+ }
+
@Override
public void onTimeStarted(long elapsedRealtimeUs, long baseUptimeUs, long baseRealtimeUs) {
mCounter.setEnabled(true, elapsedRealtimeUs / 1000);
@@ -5886,6 +6014,10 @@
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
mMobileRadioPowerState = powerState;
+
+ // Inform current RatBatteryStats that the modem active state might have changed.
+ getRatBatteryStatsLocked(mActiveRat).noteActive(active, elapsedRealtimeMs);
+
if (active) {
mMobileRadioActiveTimer.startRunningLocked(elapsedRealtimeMs);
mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtimeMs);
@@ -6307,21 +6439,86 @@
@GuardedBy("this")
public void notePhoneSignalStrengthLocked(SignalStrength signalStrength,
long elapsedRealtimeMs, long uptimeMs) {
- // Bin the strength.
- int bin = signalStrength.getLevel();
- updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, bin,
+ final int overallSignalStrength = signalStrength.getLevel();
+ final SparseIntArray perRatSignalStrength = new SparseIntArray(
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT);
+
+ // Extract signal strength level for each RAT.
+ final List<CellSignalStrength> cellSignalStrengths =
+ signalStrength.getCellSignalStrengths();
+ final int size = cellSignalStrengths.size();
+ for (int i = 0; i < size; i++) {
+ CellSignalStrength cellSignalStrength = cellSignalStrengths.get(i);
+ // Map each CellSignalStrength to a BatteryStats.RadioAccessTechnology
+ final int ratType;
+ final int level;
+ if (cellSignalStrength instanceof CellSignalStrengthNr) {
+ ratType = RADIO_ACCESS_TECHNOLOGY_NR;
+ level = cellSignalStrength.getLevel();
+ } else if (cellSignalStrength instanceof CellSignalStrengthLte) {
+ ratType = RADIO_ACCESS_TECHNOLOGY_LTE;
+ level = cellSignalStrength.getLevel();
+ } else {
+ ratType = RADIO_ACCESS_TECHNOLOGY_OTHER;
+ level = cellSignalStrength.getLevel();
+ }
+
+ // According to SignalStrength#getCellSignalStrengths(), multiple of the same
+ // cellSignalStrength can be present. Just take the highest level one for each RAT.
+ if (perRatSignalStrength.get(ratType, -1) < level) {
+ perRatSignalStrength.put(ratType, level);
+ }
+ }
+
+ notePhoneSignalStrengthLocked(overallSignalStrength, perRatSignalStrength,
+ elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Note phone signal strength change, including per RAT signal strength.
+ *
+ * @param signalStrength overall signal strength {@see SignalStrength#getLevel()}
+ * @param perRatSignalStrength signal strength of available RATs
+ */
+ @GuardedBy("this")
+ public void notePhoneSignalStrengthLocked(int signalStrength,
+ SparseIntArray perRatSignalStrength) {
+ notePhoneSignalStrengthLocked(signalStrength, perRatSignalStrength,
+ mClock.elapsedRealtime(), mClock.uptimeMillis());
+ }
+
+ /**
+ * Note phone signal strength change, including per RAT signal strength.
+ *
+ * @param signalStrength overall signal strength {@see SignalStrength#getLevel()}
+ * @param perRatSignalStrength signal strength of available RATs
+ */
+ @GuardedBy("this")
+ public void notePhoneSignalStrengthLocked(int signalStrength,
+ SparseIntArray perRatSignalStrength,
+ long elapsedRealtimeMs, long uptimeMs) {
+ // Note each RAT's signal strength.
+ final int size = perRatSignalStrength.size();
+ for (int i = 0; i < size; i++) {
+ final int rat = perRatSignalStrength.keyAt(i);
+ final int ratSignalStrength = perRatSignalStrength.valueAt(i);
+ getRatBatteryStatsLocked(rat).noteSignalStrength(ratSignalStrength, elapsedRealtimeMs);
+ }
+ updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, signalStrength,
elapsedRealtimeMs, uptimeMs);
}
@UnsupportedAppUsage
@GuardedBy("this")
- public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType) {
- notePhoneDataConnectionStateLocked(dataType, hasData, serviceType,
+ public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData,
+ @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency) {
+ notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, nrFrequency,
mClock.elapsedRealtime(), mClock.uptimeMillis());
}
@GuardedBy("this")
- public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType,
+ public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData,
+ @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency,
long elapsedRealtimeMs, long uptimeMs) {
// BatteryStats uses 0 to represent no network type.
// Telephony does not have a concept of no network type, and uses 0 to represent unknown.
@@ -6344,6 +6541,13 @@
}
}
}
+
+ final int newRat = mapNetworkTypeToRadioAccessTechnology(bin);
+ if (newRat == RADIO_ACCESS_TECHNOLOGY_NR) {
+ // Note possible frequency change for the NR RAT.
+ getRatBatteryStatsLocked(newRat).noteFrequencyRange(nrFrequency, elapsedRealtimeMs);
+ }
+
if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
if (mPhoneDataConnectionType != bin) {
mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK)
@@ -6357,6 +6561,45 @@
}
mPhoneDataConnectionType = bin;
mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtimeMs);
+
+ if (mActiveRat != newRat) {
+ getRatBatteryStatsLocked(mActiveRat).noteActive(false, elapsedRealtimeMs);
+ mActiveRat = newRat;
+ }
+ final boolean modemActive = mMobileRadioActiveTimer.isRunningLocked();
+ getRatBatteryStatsLocked(newRat).noteActive(modemActive, elapsedRealtimeMs);
+ }
+ }
+
+ @RadioAccessTechnology
+ private static int mapNetworkTypeToRadioAccessTechnology(@NetworkType int dataType) {
+ switch (dataType) {
+ case TelephonyManager.NETWORK_TYPE_NR:
+ return RADIO_ACCESS_TECHNOLOGY_NR;
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ return RADIO_ACCESS_TECHNOLOGY_LTE;
+ case TelephonyManager.NETWORK_TYPE_UNKNOWN: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_GPRS: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EDGE: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_UMTS: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_CDMA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EVDO_0: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EVDO_A: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_1xRTT: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_HSDPA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_HSUPA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_HSPA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_IDEN: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EVDO_B: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EHRPD: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_HSPAP: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_GSM: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_TD_SCDMA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_IWLAN: //fallthrough
+ return RADIO_ACCESS_TECHNOLOGY_OTHER;
+ default:
+ Slog.w(TAG, "Unhandled NetworkType (" + dataType + "), mapping to OTHER");
+ return RADIO_ACCESS_TECHNOLOGY_OTHER;
}
}
@@ -7731,6 +7974,23 @@
return mPhoneDataConnectionsTimer[dataType];
}
+ @Override public long getActiveRadioDurationMs(@RadioAccessTechnology int rat,
+ @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+ long elapsedRealtimeMs) {
+ final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat];
+ if (stats == null) return 0L;
+
+ final int freqCount = stats.perStateTimers.length;
+ if (frequencyRange < 0 || frequencyRange >= freqCount) return 0L;
+
+ final StopwatchTimer[] strengthTimers = stats.perStateTimers[frequencyRange];
+ final int strengthCount = strengthTimers.length;
+ if (signalStrength < 0 || signalStrength >= strengthCount) return 0L;
+
+ return stats.perStateTimers[frequencyRange][signalStrength].getTotalTimeLocked(
+ elapsedRealtimeMs * 1000, STATS_SINCE_CHARGED) / 1000;
+ }
+
@UnsupportedAppUsage
@Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) {
return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
@@ -10535,25 +10795,18 @@
stateCount = in.readInt();
if (stateCount != 0) {
- // Read the object from the Parcel, whether it's usable or not
- TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
- mBsi.mOnBatteryTimeBase, in, timestampMs);
- if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
- mProcStateTimeMs = counter;
- }
+ mProcStateTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+ mBsi.mOnBatteryTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+ mBsi.getCpuFreqCount(), mBsi.mClock.elapsedRealtime());
} else {
mProcStateTimeMs = null;
}
stateCount = in.readInt();
if (stateCount != 0) {
- // Read the object from the Parcel, whether it's usable or not
- TimeInFreqMultiStateCounter counter =
- new TimeInFreqMultiStateCounter(
- mBsi.mOnBatteryScreenOffTimeBase, in, timestampMs);
- if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
- mProcStateScreenOffTimeMs = counter;
- }
+ mProcStateScreenOffTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+ mBsi.mOnBatteryScreenOffTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+ mBsi.getCpuFreqCount(), mBsi.mClock.elapsedRealtime());
} else {
mProcStateScreenOffTimeMs = null;
}
@@ -12553,6 +12806,11 @@
mNetworkByteActivityCounters[i].reset(false, elapsedRealtimeUs);
mNetworkPacketActivityCounters[i].reset(false, elapsedRealtimeUs);
}
+ for (int i = 0; i < RADIO_ACCESS_TECHNOLOGY_COUNT; i++) {
+ final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[i];
+ if (stats == null) continue;
+ stats.reset(elapsedRealtimeUs);
+ }
mMobileRadioActiveTimer.reset(false, elapsedRealtimeUs);
mMobileRadioActivePerAppTimer.reset(false, elapsedRealtimeUs);
mMobileRadioActiveAdjustedTime.reset(false, elapsedRealtimeUs);
@@ -16107,6 +16365,11 @@
BATTERY_CHARGED_DELAY_MS = delay >= 0 ? delay : mParser.getInt(
KEY_BATTERY_CHARGED_DELAY_MS,
DEFAULT_BATTERY_CHARGED_DELAY_MS);
+
+ if (mHandler.hasCallbacks(mDeferSetCharging)) {
+ mHandler.removeCallbacks(mDeferSetCharging);
+ mHandler.postDelayed(mDeferSetCharging, BATTERY_CHARGED_DELAY_MS);
+ }
}
private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
@@ -16906,12 +17169,10 @@
stateCount = in.readInt();
if (stateCount != 0) {
- // Read the object from the Parcel, whether it's usable or not
- TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
- mOnBatteryTimeBase, in, mClock.elapsedRealtime());
- if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
- u.mProcStateTimeMs = counter;
- }
+ detachIfNotNull(u.mProcStateTimeMs);
+ u.mProcStateTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+ mOnBatteryTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+ getCpuFreqCount(), mClock.elapsedRealtime());
}
detachIfNotNull(u.mProcStateScreenOffTimeMs);
@@ -16920,13 +17181,9 @@
stateCount = in.readInt();
if (stateCount != 0) {
detachIfNotNull(u.mProcStateScreenOffTimeMs);
- // Read the object from the Parcel, whether it's usable or not
- TimeInFreqMultiStateCounter counter =
- new TimeInFreqMultiStateCounter(
- mOnBatteryScreenOffTimeBase, in, mClock.elapsedRealtime());
- if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
- u.mProcStateScreenOffTimeMs = counter;
- }
+ u.mProcStateScreenOffTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+ mOnBatteryScreenOffTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+ getCpuFreqCount(), mClock.elapsedRealtime());
}
if (in.readInt() != 0) {
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index be91aac..0a29fc52 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -1159,6 +1159,17 @@
: Integer.compare(a.transactionCode, b.transactionCode);
}
+ /** @hide */
+ public static void startForBluetooth(Context context) {
+ new BinderCallsStats.SettingsObserver(
+ context,
+ new BinderCallsStats(
+ new BinderCallsStats.Injector(),
+ com.android.internal.os.BinderLatencyProto.Dims.BLUETOOTH));
+
+ }
+
+
/**
* Settings observer for other processes (not system_server).
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index a50282e..2925341 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -285,6 +285,7 @@
private Insets mBackgroundInsets = Insets.NONE;
private Insets mLastBackgroundInsets = Insets.NONE;
private boolean mDrawLegacyNavigationBarBackground;
+ private boolean mDrawLegacyNavigationBarBackgroundHandled;
private PendingInsetsController mPendingInsetsController = new PendingInsetsController();
@@ -1035,10 +1036,7 @@
public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
updateColorViews(null /* insets */, true /* animate */);
if (mWindow != null) {
- final Window.Callback callback = mWindow.getCallback();
- if (callback != null) {
- callback.onSystemBarAppearanceChanged(appearance);
- }
+ mWindow.dispatchOnSystemBarAppearanceChanged(appearance);
}
}
@@ -1174,6 +1172,9 @@
mDrawLegacyNavigationBarBackground = mNavigationColorViewState.visible
&& (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) {
+ mDrawLegacyNavigationBarBackgroundHandled =
+ mWindow.onDrawLegacyNavigationBarBackgroundChanged(
+ mDrawLegacyNavigationBarBackground);
if (viewRoot != null) {
viewRoot.requestInvalidateRootRenderNode();
}
@@ -1266,7 +1267,7 @@
}
}
- if (forceConsumingNavBar) {
+ if (forceConsumingNavBar && !mDrawLegacyNavigationBarBackgroundHandled) {
mBackgroundInsets = Insets.of(mLastLeftInset, 0, mLastRightInset, mLastBottomInset);
} else {
mBackgroundInsets = Insets.NONE;
@@ -2491,7 +2492,7 @@
}
private void drawLegacyNavigationBarBackground(RecordingCanvas canvas) {
- if (!mDrawLegacyNavigationBarBackground) {
+ if (!mDrawLegacyNavigationBarBackground || mDrawLegacyNavigationBarBackgroundHandled) {
return;
}
View v = mNavigationColorViewState.view;
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index faea7706e..37c96e7 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -29,7 +29,6 @@
import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
import static android.view.WindowManager.TRANSIT_OPEN;
-import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -99,6 +98,10 @@
private static final String DEFAULT_PACKAGE = "android";
+ // TODO (b/215515255): remove once we full migrate to shell transitions
+ private static final boolean SHELL_TRANSITIONS_ENABLED =
+ SystemProperties.getBoolean("persist.debug.shell_transit", false);
+
private final Context mContext;
private final String mTag;
@@ -253,6 +256,9 @@
resId = ent.array.getResourceId(animAttr, 0);
}
}
+ if (!SHELL_TRANSITIONS_ENABLED) {
+ resId = updateToLegacyIfNeeded(resId);
+ }
resId = updateToTranslucentAnimIfNeeded(resId, transit);
if (ResourceId.isValid(resId)) {
return loadAnimationSafely(context, resId, mTag);
@@ -260,6 +266,24 @@
return null;
}
+ /**
+ * Replace animations that are not compatible with the legacy transition system with ones that
+ * are compatible with it.
+ * TODO (b/215515255): remove once we full migrate to shell transitions
+ */
+ private int updateToLegacyIfNeeded(int anim) {
+ if (anim == R.anim.activity_open_enter) {
+ return R.anim.activity_open_enter_legacy;
+ } else if (anim == R.anim.activity_open_exit) {
+ return R.anim.activity_open_exit_legacy;
+ } else if (anim == R.anim.activity_close_enter) {
+ return R.anim.activity_close_enter_legacy;
+ } else if (anim == R.anim.activity_close_exit) {
+ return R.anim.activity_close_exit_legacy;
+ }
+ return anim;
+ }
+
/** Load animation by attribute Id from a specific AnimationStyle resource. */
@Nullable
public Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr,
@@ -915,7 +939,7 @@
* animation.
*/
public HardwareBuffer createCrossProfileAppsThumbnail(
- @DrawableRes int thumbnailDrawableRes, Rect frame) {
+ Drawable thumbnailDrawable, Rect frame) {
final int width = frame.width();
final int height = frame.height();
@@ -924,14 +948,13 @@
canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
final int thumbnailSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
- final Drawable drawable = mContext.getDrawable(thumbnailDrawableRes);
- drawable.setBounds(
+ thumbnailDrawable.setBounds(
(width - thumbnailSize) / 2,
(height - thumbnailSize) / 2,
(width + thumbnailSize) / 2,
(height + thumbnailSize) / 2);
- drawable.setTint(mContext.getColor(android.R.color.white));
- drawable.draw(canvas);
+ thumbnailDrawable.setTint(mContext.getColor(android.R.color.white));
+ thumbnailDrawable.draw(canvas);
picture.endRecording();
return Bitmap.createBitmap(picture).getHardwareBuffer();
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index a5cf7ce..51eb429 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -20,15 +20,18 @@
import android.content.ComponentName;
import android.graphics.drawable.Icon;
import android.graphics.Rect;
+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;
@@ -166,6 +169,8 @@
* Used to hide the authentication dialog, e.g. when the application cancels authentication.
*/
void hideAuthenticationDialog();
+ /* Used to notify the biometric service of events that occur outside of an operation. */
+ void setBiometicContextListener(in IBiometricContextListener listener);
/**
* Sets an instance of IUdfpsHbmListener for UdfpsController.
@@ -293,4 +298,15 @@
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 accb986..0c45e5b 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -20,9 +20,11 @@
import android.content.ComponentName;
import android.graphics.drawable.Icon;
import android.graphics.Rect;
+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;
@@ -32,6 +34,7 @@
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;
@@ -125,6 +128,8 @@
void onBiometricError(int modality, int error, int vendorCode);
// Used to hide the authentication dialog, e.g. when the application cancels authentication
void hideAuthenticationDialog();
+ // Used to notify the biometric service of events that occur outside of an operation.
+ void setBiometicContextListener(in IBiometricContextListener listener);
/**
* Sets an instance of IUdfpsHbmListener for UdfpsController.
@@ -193,4 +198,15 @@
*/
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
similarity index 68%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl
rename to core/java/com/android/internal/statusbar/IUndoMediaTransferCallback.aidl
index b47be87..3dd2980 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/internal/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java
index bfe4323..17b84ff 100644
--- a/core/java/com/android/internal/util/UserIcons.java
+++ b/core/java/com/android/internal/util/UserIcons.java
@@ -16,6 +16,7 @@
package com.android.internal.util;
+import android.annotation.ColorInt;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -72,9 +73,30 @@
// Return colored icon instead
colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length];
}
+ return getDefaultUserIconInColor(resources, resources.getColor(colorResId, null));
+ }
+
+ /**
+ * Returns a default user icon in a particular color.
+ *
+ * @param resources resources object to fetch the user icon
+ * @param color the color used for the icon
+ */
+ public static Drawable getDefaultUserIconInColor(Resources resources, @ColorInt int color) {
Drawable icon = resources.getDrawable(R.drawable.ic_account_circle, null).mutate();
- icon.setColorFilter(resources.getColor(colorResId, null), Mode.SRC_IN);
+ icon.setColorFilter(color, Mode.SRC_IN);
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
return icon;
}
+
+ /**
+ * Returns an array containing colors to be used for default user icons.
+ */
+ public static int[] getUserIconColors(Resources resources) {
+ int[] result = new int[USER_ICON_COLORS.length];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = resources.getColor(USER_ICON_COLORS[i], null);
+ }
+ return result;
+ }
}
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index da24832..d2bc344 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -37,7 +37,8 @@
*/
oneway interface IInputMethod {
void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
- int configChanges, boolean stylusHwSupported);
+ int configChanges, boolean stylusHwSupported,
+ boolean shouldShowImeSwitcherWhenImeIsShown);
void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo,
in IInlineSuggestionsRequestCallback cb);
@@ -47,7 +48,10 @@
void unbindInput();
void startInput(in IBinder startInputToken, in IInputContext inputContext,
- in EditorInfo attribute, boolean restarting);
+ in EditorInfo attribute, boolean restarting,
+ boolean shouldShowImeSwitcherWhenImeIsShown);
+
+ void onShouldShowImeSwitcherWhenImeIsShownChanged(boolean shouldShowImeSwitcherWhenImeIsShown);
void createSession(in InputChannel channel, IInputSessionCallback callback);
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 39f17e5..93864fa 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -237,6 +237,10 @@
// 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 @@
return mAllowIgnoreLocationSettings;
}
+ public ArraySet<String> getBgRestrictionExemption() {
+ return mBgRestrictionExemption;
+ }
+
public ArraySet<String> getLinkedApps() {
return mLinkedApps;
}
@@ -1049,6 +1057,20 @@
}
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.bp b/core/jni/Android.bp
index 430d84e..8bb9a0a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -195,7 +195,6 @@
"android_util_FileObserver.cpp",
"android/opengl/poly_clip.cpp", // TODO: .arm
"android/opengl/util.cpp",
- "android_server_NetworkManagementSocketTagger.cpp",
"android_ddm_DdmHandleNativeHeap.cpp",
"android_backup_BackupDataInput.cpp",
"android_backup_BackupDataOutput.cpp",
@@ -310,6 +309,8 @@
"libdl_android",
"libtimeinstate",
"server_configurable_flags",
+ // TODO: delete when ConnectivityT moves to APEX.
+ "libframework-connectivity-tiramisu-jni",
],
export_shared_lib_headers: [
// our headers include libnativewindow's public headers
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f4296be..cde71cf 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -164,7 +164,6 @@
extern int register_android_text_Hyphenator(JNIEnv *env);
extern int register_android_opengl_classes(JNIEnv *env);
extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
-extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env);
extern int register_android_backup_BackupDataInput(JNIEnv *env);
extern int register_android_backup_BackupDataOutput(JNIEnv *env);
extern int register_android_backup_FileBackupHelperBase(JNIEnv *env);
@@ -1623,7 +1622,6 @@
REG_JNI(register_android_media_midi),
REG_JNI(register_android_opengl_classes),
- REG_JNI(register_android_server_NetworkManagementSocketTagger),
REG_JNI(register_android_ddm_DdmHandleNativeHeap),
REG_JNI(register_android_backup_BackupDataInput),
REG_JNI(register_android_backup_BackupDataOutput),
diff --git a/core/jni/android/opengl/OWNERS b/core/jni/android/opengl/OWNERS
new file mode 100644
index 0000000..ce4b907
--- /dev/null
+++ b/core/jni/android/opengl/OWNERS
@@ -0,0 +1 @@
+file:/graphics/java/android/graphics/OWNERS
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 82601ba..d852265 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -643,6 +643,8 @@
return (type == GL_UNSIGNED_SHORT_5_6_5 && internalformat == GL_RGB);
case ANDROID_BITMAP_FORMAT_RGBA_F16:
return (type == GL_HALF_FLOAT && internalformat == GL_RGBA16F);
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ return (type == GL_UNSIGNED_INT_2_10_10_10_REV && internalformat == GL_RGB10_A2);
default:
break;
}
@@ -676,6 +678,8 @@
return GL_RGB;
case ANDROID_BITMAP_FORMAT_RGBA_F16:
return GL_RGBA16F;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ return GL_RGB10_A2;
default:
return -1;
}
@@ -693,6 +697,8 @@
return GL_UNSIGNED_SHORT_5_6_5;
case ANDROID_BITMAP_FORMAT_RGBA_F16:
return GL_HALF_FLOAT;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ return GL_UNSIGNED_INT_2_10_10_10_REV;
default:
return -1;
}
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index d039bcf..78b403c 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -133,6 +133,18 @@
MakeGlobalRefOrDie(_env, _env->CallObjectMethod(empty.get(), stringOffsets.intern));
}
+uint64_t htonll(uint64_t ll) {
+ constexpr uint32_t kBytesToTest = 0x12345678;
+ constexpr uint8_t kFirstByte = (const uint8_t &)kBytesToTest;
+ constexpr bool kIsLittleEndian = kFirstByte == 0x78;
+
+ if constexpr (kIsLittleEndian) {
+ return static_cast<uint64_t>(htonl(ll & 0xffffffff)) << 32 | htonl(ll >> 32);
+ } else {
+ return ll;
+ }
+}
+
static jstring getJavaInternedString(JNIEnv *env, const String8 &string) {
if (string == "") {
return gStringOffsets.emptyString;
@@ -193,7 +205,8 @@
int32_t id = nativeSensor.getId();
env->CallVoidMethod(sensor, sensorOffsets.setId, id);
Sensor::uuid_t uuid = nativeSensor.getUuid();
- env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, uuid.i64[0], uuid.i64[1]);
+ env->CallVoidMethod(sensor, sensorOffsets.setUuid, htonll(uuid.i64[0]),
+ htonll(uuid.i64[1]));
}
return sensor;
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index e13b788..edc8c5b 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -204,6 +204,12 @@
jclass gAudioProfileClass;
jmethodID gAudioProfileCstor;
+static struct {
+ jfieldID mSamplingRates;
+ jfieldID mChannelMasks;
+ jfieldID mChannelIndexMasks;
+ jfieldID mEncapsulationType;
+} gAudioProfileFields;
jclass gVibratorClass;
static struct {
@@ -1288,7 +1294,7 @@
audioFormatFromNative(nAudioProfile->format), jSamplingRates.get(),
jChannelMasks.get(), jChannelIndexMasks.get(), encapsulationType);
- if (jAudioProfile == nullptr) {
+ if (*jAudioProfile == nullptr) {
return AUDIO_JAVA_ERROR;
}
@@ -1340,81 +1346,50 @@
jStatus = (jint)AUDIO_JAVA_ERROR;
goto exit;
}
+
for (size_t i = 0; i < nAudioPort->num_audio_profiles; ++i) {
- size_t numPositionMasks = 0;
- size_t numIndexMasks = 0;
- // count up how many masks are positional and indexed
- for (size_t index = 0; index < nAudioPort->audio_profiles[i].num_channel_masks; index++) {
- const audio_channel_mask_t mask = nAudioPort->audio_profiles[i].channel_masks[index];
- if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
- numIndexMasks++;
- } else {
- numPositionMasks++;
- }
- }
-
- ScopedLocalRef<jintArray> jSamplingRates(env,
- env->NewIntArray(nAudioPort->audio_profiles[i]
- .num_sample_rates));
- ScopedLocalRef<jintArray> jChannelMasks(env, env->NewIntArray(numPositionMasks));
- ScopedLocalRef<jintArray> jChannelIndexMasks(env, env->NewIntArray(numIndexMasks));
- if (!jSamplingRates.get() || !jChannelMasks.get() || !jChannelIndexMasks.get()) {
+ jobject jAudioProfile = nullptr;
+ jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &nAudioPort->audio_profiles[i],
+ useInMask);
+ if (jStatus != NO_ERROR) {
jStatus = (jint)AUDIO_JAVA_ERROR;
goto exit;
}
+ env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add, jAudioProfile);
- if (nAudioPort->audio_profiles[i].num_sample_rates) {
- env->SetIntArrayRegion(jSamplingRates.get(), 0 /*start*/,
- nAudioPort->audio_profiles[i].num_sample_rates,
- (jint *)nAudioPort->audio_profiles[i].sample_rates);
- }
-
- // put the masks in the output arrays
- for (size_t maskIndex = 0, posMaskIndex = 0, indexedMaskIndex = 0;
- maskIndex < nAudioPort->audio_profiles[i].num_channel_masks; maskIndex++) {
- const audio_channel_mask_t mask =
- nAudioPort->audio_profiles[i].channel_masks[maskIndex];
- if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
- jint jMask = audio_channel_mask_get_bits(mask);
- env->SetIntArrayRegion(jChannelIndexMasks.get(), indexedMaskIndex++, 1, &jMask);
- } else {
- jint jMask =
- useInMask ? inChannelMaskFromNative(mask) : outChannelMaskFromNative(mask);
- env->SetIntArrayRegion(jChannelMasks.get(), posMaskIndex++, 1, &jMask);
- }
- }
-
- int encapsulationType;
- if (audioEncapsulationTypeFromNative(nAudioPort->audio_profiles[i].encapsulation_type,
- &encapsulationType) != NO_ERROR) {
- ALOGW("Unknown encapsualtion type for JAVA API: %u",
- nAudioPort->audio_profiles[i].encapsulation_type);
- continue;
- }
-
- ScopedLocalRef<jobject>
- jAudioProfile(env,
- env->NewObject(gAudioProfileClass, gAudioProfileCstor,
- audioFormatFromNative(
- nAudioPort->audio_profiles[i].format),
- jSamplingRates.get(), jChannelMasks.get(),
- jChannelIndexMasks.get(), encapsulationType));
- if (jAudioProfile == nullptr) {
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
- }
- env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add, jAudioProfile.get());
if (nAudioPort->audio_profiles[i].format == AUDIO_FORMAT_PCM_FLOAT) {
hasFloat = true;
} else if (jPcmFloatProfileFromExtendedInteger.get() == nullptr &&
audio_is_linear_pcm(nAudioPort->audio_profiles[i].format) &&
audio_bytes_per_sample(nAudioPort->audio_profiles[i].format) > 2) {
+ ScopedLocalRef<jintArray>
+ jSamplingRates(env,
+ (jintArray)
+ env->GetObjectField(jAudioProfile,
+ gAudioProfileFields.mSamplingRates));
+ ScopedLocalRef<jintArray>
+ jChannelMasks(env,
+ (jintArray)
+ env->GetObjectField(jAudioProfile,
+ gAudioProfileFields.mChannelMasks));
+ ScopedLocalRef<jintArray>
+ jChannelIndexMasks(env,
+ (jintArray)env->GetObjectField(jAudioProfile,
+ gAudioProfileFields
+ .mChannelIndexMasks));
+ int encapsulationType =
+ env->GetIntField(jAudioProfile, gAudioProfileFields.mEncapsulationType);
+
jPcmFloatProfileFromExtendedInteger.reset(
env->NewObject(gAudioProfileClass, gAudioProfileCstor,
audioFormatFromNative(AUDIO_FORMAT_PCM_FLOAT),
jSamplingRates.get(), jChannelMasks.get(),
jChannelIndexMasks.get(), encapsulationType));
}
+
+ if (jAudioProfile != nullptr) {
+ env->DeleteLocalRef(jAudioProfile);
+ }
}
if (!hasFloat && jPcmFloatProfileFromExtendedInteger.get() != nullptr) {
// R and earlier compatibility - add ENCODING_PCM_FLOAT to the end
@@ -3285,6 +3260,14 @@
jclass audioProfileClass = FindClassOrDie(env, "android/media/AudioProfile");
gAudioProfileClass = MakeGlobalRefOrDie(env, audioProfileClass);
gAudioProfileCstor = GetMethodIDOrDie(env, audioProfileClass, "<init>", "(I[I[I[II)V");
+ gAudioProfileFields.mSamplingRates =
+ GetFieldIDOrDie(env, audioProfileClass, "mSamplingRates", "[I");
+ gAudioProfileFields.mChannelMasks =
+ GetFieldIDOrDie(env, audioProfileClass, "mChannelMasks", "[I");
+ gAudioProfileFields.mChannelIndexMasks =
+ GetFieldIDOrDie(env, audioProfileClass, "mChannelIndexMasks", "[I");
+ gAudioProfileFields.mEncapsulationType =
+ GetFieldIDOrDie(env, audioProfileClass, "mEncapsulationType", "I");
jclass audioDescriptorClass = FindClassOrDie(env, "android/media/AudioDescriptor");
gAudioDescriptorClass = MakeGlobalRefOrDie(env, audioDescriptorClass);
diff --git a/core/jni/android_server_NetworkManagementSocketTagger.cpp b/core/jni/android_server_NetworkManagementSocketTagger.cpp
deleted file mode 100644
index 9734ab9..0000000
--- a/core/jni/android_server_NetworkManagementSocketTagger.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2011, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "NMST_QTagUidNative"
-
-#include <android/multinetwork.h>
-#include <cutils/qtaguid.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <nativehelper/JNIPlatformHelp.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <utils/Log.h>
-#include <utils/misc.h>
-
-#include "jni.h"
-
-namespace android {
-
-static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor,
- jint tagNum, jint uid) {
- int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-
- if (env->ExceptionCheck()) {
- ALOGE("Can't get FileDescriptor num");
- return (jint)-1;
- }
-
- int res = android_tag_socket_with_uid(userFd, tagNum, uid);
- if (res < 0) {
- return (jint)-errno;
- }
- return (jint)res;
-}
-
-static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) {
- int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-
- if (env->ExceptionCheck()) {
- ALOGE("Can't get FileDescriptor num");
- return (jint)-1;
- }
-
- int res = android_untag_socket(userFd);
- if (res < 0) {
- return (jint)-errno;
- }
- return (jint)res;
-}
-
-static const JNINativeMethod gQTagUidMethods[] = {
- { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*)tagSocketFd},
- { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*)untagSocketFd},
-};
-
-int register_android_server_NetworkManagementSocketTagger(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "com/android/server/NetworkManagementSocketTagger", gQTagUidMethods, NELEM(gQTagUidMethods));
-}
-
-};
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index a8cf253..9915913 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -104,6 +104,7 @@
jfieldID hdrCapabilities;
jfieldID autoLowLatencyModeSupported;
jfieldID gameContentTypeSupported;
+ jfieldID preferredBootDisplayMode;
} gDynamicDisplayInfoClassInfo;
static struct {
@@ -1301,6 +1302,9 @@
env->SetBooleanField(object, gDynamicDisplayInfoClassInfo.gameContentTypeSupported,
info.gameContentTypeSupported);
+
+ env->SetIntField(object, gDynamicDisplayInfoClassInfo.preferredBootDisplayMode,
+ info.preferredBootDisplayMode);
return object;
}
@@ -1638,6 +1642,27 @@
}
}
+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 @@
(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 @@
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/proto/android/server/Android.bp b/core/proto/android/server/Android.bp
new file mode 100644
index 0000000..362daa7
--- /dev/null
+++ b/core/proto/android/server/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "srcs_bluetooth_manager_service_proto",
+ srcs: [
+ "bluetooth_manager_service.proto",
+ ],
+ visibility: ["//packages/modules/Bluetooth:__subpackages__"],
+}
+
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c9bd222..85504ce 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -719,6 +719,7 @@
<protected-broadcast android:name="android.safetycenter.action.REFRESH_SAFETY_SOURCES" />
<protected-broadcast android:name="android.app.action.DEVICE_POLICY_RESOURCE_UPDATED" />
<protected-broadcast android:name="android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER" />
+ <protected-broadcast android:name="android.service.autofill.action.DELAYED_FILL" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -976,61 +977,6 @@
android:permissionFlags="softRestricted|immutablyRestricted"
android:protectionLevel="dangerous" />
- <!-- Required to be able to read audio files from shared storage.
- <p>Protection level: dangerous -->
- <permission-group android:name="android.permission-group.READ_MEDIA_AURAL"
- android:icon="@drawable/perm_group_read_media_aural"
- android:label="@string/permgrouplab_readMediaAural"
- android:description="@string/permgroupdesc_readMediaAural"
- android:priority="950" />
-
- <!-- Allows an application to read audio files from external storage.
- <p>This permission is enforced starting in API level
- {@link android.os.Build.VERSION_CODES#TIRAMISU}.
- For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
- targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
- must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
- <p>Protection level: dangerous -->
- <permission android:name="android.permission.READ_MEDIA_AUDIO"
- android:permissionGroup="android.permission-group.UNDEFINED"
- android:label="@string/permlab_readMediaAudio"
- android:description="@string/permdesc_readMediaAudio"
- android:protectionLevel="dangerous" />
-
- <!-- Required to be able to read image and video files from shared storage.
- <p>Protection level: dangerous -->
- <permission-group android:name="android.permission-group.READ_MEDIA_VISUAL"
- android:icon="@drawable/perm_group_read_media_visual"
- android:label="@string/permgrouplab_readMediaVisual"
- android:description="@string/permgroupdesc_readMediaVisual"
- android:priority="1000" />
-
- <!-- Allows an application to read audio files from external storage.
- <p>This permission is enforced starting in API level
- {@link android.os.Build.VERSION_CODES#TIRAMISU}.
- For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
- targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
- must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
- <p>Protection level: dangerous -->
- <permission android:name="android.permission.READ_MEDIA_VIDEO"
- android:permissionGroup="android.permission-group.UNDEFINED"
- android:label="@string/permlab_readMediaVideo"
- android:description="@string/permdesc_readMediaVideo"
- android:protectionLevel="dangerous" />
-
- <!-- Allows an application to read image files from external storage.
- <p>This permission is enforced starting in API level
- {@link android.os.Build.VERSION_CODES#TIRAMISU}.
- For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
- targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
- must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
- <p>Protection level: dangerous -->
- <permission android:name="android.permission.READ_MEDIA_IMAGE"
- android:permissionGroup="android.permission-group.UNDEFINED"
- android:label="@string/permlab_readMediaImage"
- android:description="@string/permdesc_readMediaImage"
- android:protectionLevel="dangerous" />
-
<!-- Allows an application to write to external storage.
<p class="note"><strong>Note:</strong> If <em>both</em> your <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
@@ -1144,6 +1090,15 @@
android:description="@string/permdesc_accessBackgroundLocation"
android:protectionLevel="dangerous|instant" />
+ <!-- Allows an application (emergency or advanced driver-assistance app) to bypass
+ location settings.
+ <p>Not for use by third-party applications.
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.LOCATION_BYPASS"
+ android:protectionLevel="signature|privileged"/>
+
<!-- ====================================================================== -->
<!-- Permissions for accessing the call log -->
<!-- ====================================================================== -->
@@ -1647,7 +1602,7 @@
android:protectionLevel="normal"
android:permissionFlags="removed"/>
- <!-- @hide We need to keep this around for backwards compatibility -->
+ <!-- @SystemApi @hide We need to keep this around for backwards compatibility -->
<permission android:name="android.permission.WRITE_SMS"
android:protectionLevel="normal"
android:permissionFlags="removed"/>
@@ -1725,7 +1680,7 @@
<permission android:name="android.permission.RECEIVE_EMERGENCY_BROADCAST"
android:protectionLevel="signature|privileged" />
- <!-- Allows an application to monitor incoming Bluetooth MAP messages, to record
+ <!-- @SystemApi Allows an application to monitor incoming Bluetooth MAP messages, to record
or perform processing on them. -->
<!-- @hide -->
<permission android:name="android.permission.RECEIVE_BLUETOOTH_MAP"
@@ -4170,6 +4125,16 @@
<permission android:name="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by a
+ android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE"
+ android:protectionLevel="signature" />
+
+
<!-- Must be declared by a android.service.musicrecognition.MusicRecognitionService,
to ensure that only the system can bind to it.
@SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -4672,6 +4637,13 @@
<permission android:name="android.permission.READ_FRAME_BUFFER"
android:protectionLevel="signature|recents" />
+ <!-- @SystemApi Allows an application to change the touch mode state.
+ Without this permission, an app can only change the touch mode
+ if it currently has focus.
+ @hide -->
+ <permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to use InputFlinger's low level features.
@hide -->
<permission android:name="android.permission.ACCESS_INPUT_FLINGER"
@@ -5862,6 +5834,13 @@
<permission android:name="android.permission.MANAGE_SMARTSPACE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to manage the wallpaper effects
+ generation service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION"
+ android:protectionLevel="signature" />
+
+
<!-- Allows an app to set the theme overlay in /vendor/overlay
being used.
@hide <p>Not for use by third-party applications.</p> -->
@@ -5961,6 +5940,10 @@
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND"
android:protectionLevel="signature" />
+ <!-- @hide Permission that suppresses the notification when the clipboard is accessed.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION"
+ android:protectionLevel="signature" />
<!-- @SystemApi Allows modifying accessibility state.
@hide -->
@@ -6164,6 +6147,12 @@
<permission android:name="android.permission.ACCESS_FPS_COUNTER"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows the GameService provider to create GameSession and call GameSession
+ APIs and overlay a view on top of the game's Activity.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_GAME_ACTIVITY"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager
when they are performing reboot-blocking work.
@hide -->
@@ -6290,6 +6279,15 @@
<permission android:name="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an app to set keep-clear areas without restrictions on the size or
+ number of keep-clear areas (see {@link android.view.View#setPreferKeepClearRects}).
+ When the system arranges floating windows onscreen, it might decide to ignore keep-clear
+ areas from windows, whose owner does not have this permission.
+ @hide
+ -->
+ <permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS"
+ android:protectionLevel="signature|privileged" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/anim/activity_close_enter.xml b/core/res/res/anim/activity_close_enter.xml
index 9fa7c54..0fefb51 100644
--- a/core/res/res/anim/activity_close_enter.xml
+++ b/core/res/res/anim/activity_close_enter.xml
@@ -19,16 +19,37 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
- <scale
- android:fromXScale="1.1"
- android:toXScale="1"
- android:fromYScale="1.1"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
+
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:startOffset="0"
+ android:duration="450" />
+
+ <translate
+ android:fromXDelta="-10%"
+ android:toXDelta="0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/fast_out_extra_slow_in"
- android:duration="400"/>
+ android:startOffset="0"
+ android:duration="450" />
+
+ <extend
+ android:fromExtendLeft="0"
+ android:fromExtendTop="0"
+ android:fromExtendRight="10%"
+ android:fromExtendBottom="0"
+ android:toExtendLeft="0"
+ android:toExtendTop="0"
+ android:toExtendRight="10%"
+ android:toExtendBottom="0"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/activity_close_enter_legacy.xml b/core/res/res/anim/activity_close_enter_legacy.xml
new file mode 100644
index 0000000..9fa7c54
--- /dev/null
+++ b/core/res/res/anim/activity_close_enter_legacy.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+ <scale
+ android:fromXScale="1.1"
+ android:toXScale="1"
+ android:fromYScale="1.1"
+ android:toYScale="1"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:duration="400"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/activity_close_exit.xml b/core/res/res/anim/activity_close_exit.xml
index 1599ae8..f807c26 100644
--- a/core/res/res/anim/activity_close_exit.xml
+++ b/core/res/res/anim/activity_close_exit.xml
@@ -18,27 +18,38 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top">
+ android:shareInterpolator="false">
+
<alpha
- android:fromAlpha="1"
+ android:fromAlpha="1.0"
android:toAlpha="0.0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/linear"
- android:startOffset="33"
- android:duration="50"/>
- <scale
- android:fromXScale="1"
- android:toXScale="0.9"
- android:fromYScale="1"
- android:toYScale="0.9"
- android:pivotX="50%"
- android:pivotY="50%"
+ android:startOffset="35"
+ android:duration="83" />
+
+ <translate
+ android:fromXDelta="0"
+ android:toXDelta="10%"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/fast_out_extra_slow_in"
- android:duration="400"/>
+ android:startOffset="0"
+ android:duration="450" />
+
+ <extend
+ android:fromExtendLeft="10%"
+ android:fromExtendTop="0"
+ android:fromExtendRight="0"
+ android:fromExtendBottom="0"
+ android:toExtendLeft="10%"
+ android:toExtendTop="0"
+ android:toExtendRight="0"
+ android:toExtendBottom="0"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
</set>
diff --git a/core/res/res/anim/activity_close_exit_legacy.xml b/core/res/res/anim/activity_close_exit_legacy.xml
new file mode 100644
index 0000000..1599ae8
--- /dev/null
+++ b/core/res/res/anim/activity_close_exit_legacy.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false"
+ android:zAdjustment="top">
+ <alpha
+ android:fromAlpha="1"
+ android:toAlpha="0.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:startOffset="33"
+ android:duration="50"/>
+ <scale
+ android:fromXScale="1"
+ android:toXScale="0.9"
+ android:fromYScale="1"
+ android:toYScale="0.9"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:duration="400"/>
+</set>
diff --git a/core/res/res/anim/activity_open_enter.xml b/core/res/res/anim/activity_open_enter.xml
index 38d3e8ed..1674dab 100644
--- a/core/res/res/anim/activity_open_enter.xml
+++ b/core/res/res/anim/activity_open_enter.xml
@@ -18,6 +18,7 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
+
<alpha
android:fromAlpha="0"
android:toAlpha="1.0"
@@ -26,17 +27,27 @@
android:fillAfter="true"
android:interpolator="@interpolator/linear"
android:startOffset="50"
- android:duration="50"/>
- <scale
- android:fromXScale="0.85"
- android:toXScale="1"
- android:fromYScale="0.85"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
+ android:duration="83" />
+
+ <translate
+ android:fromXDelta="10%"
+ android:toXDelta="0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/fast_out_extra_slow_in"
- android:duration="400"/>
+ android:duration="450" />
+
+ <extend
+ android:fromExtendLeft="10%"
+ android:fromExtendTop="0"
+ android:fromExtendRight="0"
+ android:fromExtendBottom="0"
+ android:toExtendLeft="10%"
+ android:toExtendTop="0"
+ android:toExtendRight="0"
+ android:toExtendBottom="0"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
</set>
diff --git a/core/res/res/anim/activity_open_enter_legacy.xml b/core/res/res/anim/activity_open_enter_legacy.xml
new file mode 100644
index 0000000..38d3e8ed
--- /dev/null
+++ b/core/res/res/anim/activity_open_enter_legacy.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+/*
+** Copyright 2009, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+ <alpha
+ android:fromAlpha="0"
+ android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:startOffset="50"
+ android:duration="50"/>
+ <scale
+ android:fromXScale="0.85"
+ android:toXScale="1"
+ android:fromYScale="0.85"
+ android:toYScale="1"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:duration="400"/>
+</set>
diff --git a/core/res/res/anim/activity_open_exit.xml b/core/res/res/anim/activity_open_exit.xml
index 3865d21..372f2c8 100644
--- a/core/res/res/anim/activity_open_exit.xml
+++ b/core/res/res/anim/activity_open_exit.xml
@@ -19,27 +19,36 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
- <!-- Fade out, over a black surface, which simulates a black scrim -->
<alpha
- android:fromAlpha="1"
- android:toAlpha="0.4"
+ android:fromAlpha="1.0"
+ android:toAlpha="1.0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="83"
- android:duration="167"/>
+ android:interpolator="@interpolator/standard_accelerate"
+ android:startOffset="0"
+ android:duration="450" />
- <scale
- android:fromXScale="1"
- android:toXScale="1.05"
- android:fromYScale="1"
- android:toYScale="1.05"
- android:pivotX="50%"
- android:pivotY="50%"
+ <translate
+ android:fromXDelta="0"
+ android:toXDelta="-10%"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/fast_out_extra_slow_in"
- android:duration="400"/>
+ android:startOffset="0"
+ android:duration="450" />
+
+ <extend
+ android:fromExtendLeft="0"
+ android:fromExtendTop="0"
+ android:fromExtendRight="10%"
+ android:fromExtendBottom="0"
+ android:toExtendLeft="0"
+ android:toExtendTop="0"
+ android:toExtendRight="10%"
+ android:toExtendBottom="0"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/activity_open_exit_legacy.xml b/core/res/res/anim/activity_open_exit_legacy.xml
new file mode 100644
index 0000000..3865d21
--- /dev/null
+++ b/core/res/res/anim/activity_open_exit_legacy.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+/*
+** Copyright 2009, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+
+ <!-- Fade out, over a black surface, which simulates a black scrim -->
+ <alpha
+ android:fromAlpha="1"
+ android:toAlpha="0.4"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:startOffset="83"
+ android:duration="167"/>
+
+ <scale
+ android:fromXScale="1"
+ android:toXScale="1.05"
+ android:fromYScale="1"
+ android:toYScale="1.05"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:duration="400"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/drawable-car/car_checkbox.xml b/core/res/res/drawable-car/car_checkbox.xml
deleted file mode 100644
index 083a7aa..0000000
--- a/core/res/res/drawable-car/car_checkbox.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item
- android:width="@*android:dimen/car_primary_icon_size"
- android:height="@*android:dimen/car_primary_icon_size"
- android:drawable="@drawable/btn_check_material_anim"/>
- <item
- android:width="@*android:dimen/car_primary_icon_size"
- android:height="@*android:dimen/car_primary_icon_size"
- android:drawable="@drawable/car_checkbox_background"/>
-</layer-list>
diff --git a/core/res/res/drawable-car/car_checkbox_background.xml b/core/res/res/drawable-car/car_checkbox_background.xml
deleted file mode 100644
index 69dcdbb..0000000
--- a/core/res/res/drawable-car/car_checkbox_background.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_focused="true" android:state_pressed="true">
- <shape android:shape="rectangle">
- <solid android:color="#8A0041BE" />
- <stroke android:width="4dp" android:color="#0041BE" />
- </shape>
- </item>
- <item android:state_focused="true">
- <shape android:shape="rectangle">
- <solid android:color="#3D0059B3" />
- <stroke android:width="8dp" android:color="#0059B3" />
- </shape>
- </item>
-</selector>
diff --git a/core/res/res/drawable/perm_group_read_media_aural.xml b/core/res/res/drawable/perm_group_read_media_aural.xml
deleted file mode 100644
index 6fc9c69..0000000
--- a/core/res/res/drawable/perm_group_read_media_aural.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M10,21q-1.65,0 -2.825,-1.175Q6,18.65 6,17q0,-1.65 1.175,-2.825Q8.35,13 10,13q0.575,0 1.063,0.137 0.487,0.138 0.937,0.413V3h6v4h-4v10q0,1.65 -1.175,2.825Q11.65,21 10,21z"/>
-</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/perm_group_read_media_visual.xml b/core/res/res/drawable/perm_group_read_media_visual.xml
deleted file mode 100644
index a5db271..0000000
--- a/core/res/res/drawable/perm_group_read_media_visual.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M9,14h10l-3.45,-4.5 -2.3,3 -1.55,-2zM8,18q-0.825,0 -1.412,-0.587Q6,16.825 6,16L6,4q0,-0.825 0.588,-1.413Q7.175,2 8,2h12q0.825,0 1.413,0.587Q22,3.175 22,4v12q0,0.825 -0.587,1.413Q20.825,18 20,18zM8,16h12L20,4L8,4v12zM4,22q-0.825,0 -1.413,-0.587Q2,20.825 2,20L2,6h2v14h14v2zM8,4v12L8,4z"/>
-</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml
new file mode 100644
index 0000000..44ed6f2
--- /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/attrs.xml b/core/res/res/values/attrs.xml
index 7916ef4..afe0f1b 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2885,7 +2885,7 @@
<code>public void sayHello(View v)</code> method of your context
(typically, your Activity).
{@deprecated View actually traverses the Context
- hierarchy looking for the relevant method, which is fragile (an intermediate
+ hierarchy looking for the relevant method, which is fragile (an intermediate
ContextWrapper adding a same-named method would change behavior) and restricts
bytecode optimizers such as R8. Instead, use View.setOnClickListener.}-->
<attr name="onClick" format="string" />
@@ -6975,6 +6975,34 @@
<attr name="toBottom" format="fraction" />
</declare-styleable>
+ <!-- Defines the ExtendAnimation used to extend windows during animations -->
+ <declare-styleable name="ExtendAnimation">
+ <!-- Defines the amount a window should be extended outward from the left at
+ the start of the animation. -->
+ <attr name="fromExtendLeft" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the top at
+ the start of the animation. -->
+ <attr name="fromExtendTop" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the right at
+ the start of the animation. -->
+ <attr name="fromExtendRight" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the bottom at
+ the start of the animation. -->
+ <attr name="fromExtendBottom" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the left by
+ the end of the animation by transitioning from the fromExtendLeft amount. -->
+ <attr name="toExtendLeft" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the top by
+ the end of the animation by transitioning from the fromExtendTop amount. -->
+ <attr name="toExtendTop" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the right by
+ the end of the animation by transitioning from the fromExtendRight amount. -->
+ <attr name="toExtendRight" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the bottom by
+ the end of the animation by transitioning from the fromExtendBottom amount. -->
+ <attr name="toExtendBottom" format="float|fraction" />
+ </declare-styleable>
+
<declare-styleable name="LayoutAnimation">
<!-- Fraction of the animation duration used to delay the beginning of
the animation of each child. -->
@@ -7827,7 +7855,7 @@
<!-- Name of a method on the Context used to inflate the menu that will be
called when the item is clicked.
- {@deprecated Menu actually traverses the Context hierarchy looking for the
+ {@deprecated Menu actually traverses the Context hierarchy looking for the
relevant method, which is fragile (an intermediate ContextWrapper adding a
same-named method would change behavior) and restricts bytecode optimizers
such as R8. Instead, use MenuItem.setOnMenuItemClickListener.} -->
@@ -8847,6 +8875,22 @@
<attr name="gameSessionService" format="string" />
</declare-styleable>
+ <!-- Use <code>game-mode-config</code> as the root tag of the XML resource that
+ describes a GameModeConfig.
+ Described here are the attributes that can be included in that tag. -->
+ <declare-styleable name="GameModeConfig">
+ <!-- Set true to opt in BATTERY mode. -->
+ <attr name="supportsBatteryGameMode" format="boolean" />
+ <!-- Set true to opt in PERFORMANCE mode. -->
+ <attr name="supportsPerformanceGameMode" format="boolean" />
+ <!-- Set true to enable ANGLE. -->
+ <attr name="allowGameAngleDriver" format="boolean" />
+ <!-- Set true to allow resolution downscaling intervention. -->
+ <attr name="allowGameDownscaling" format="boolean" />
+ <!-- Set true to allow FPS override intervention. -->
+ <attr name="allowGameFpsOverride" format="boolean" />
+ </declare-styleable>
+
<!-- Use <code>voice-enrollment-application</code>
as the root tag of the XML resource that escribes the supported keyphrases (hotwords)
by the enrollment application.
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 3a2fb6e..cb40e86 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2839,6 +2839,14 @@
<attr name="path" />
<attr name="minSdkVersion" />
<attr name="maxSdkVersion" />
+ <!-- The order in which the apex system services are initiated. When there are dependencies
+ among apex system services, setting this attribute for each of them ensures that they are
+ created in the order required by those dependencies. The apex-system-services that are
+ started manually within SystemServer ignore the initOrder and are not considered for
+ automatic starting of the other services.
+ The value is a simple integer, with higher number being initialized first. If not specified,
+ the default order is 0. -->
+ <attr name="initOrder" format="integer" />
</declare-styleable>
<!-- The <code>receiver</code> tag declares an
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 78841fc..53cf463 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>
@@ -2612,6 +2618,10 @@
will be locked. -->
<bool name="config_multiuserDelayUserDataLocking">false</bool>
+ <!-- Whether to automatically switch a non-primary user back to the primary user after a
+ timeout when the device is docked. -->
+ <bool name="config_enableTimeoutToUserZeroWhenDocked">false</bool>
+
<!-- Whether to only install system packages on a user if they're allowlisted for that user
type. These are flags and can be freely combined.
0 - disable allowlist (install all system packages; no logging)
@@ -2707,6 +2717,9 @@
Values are bandwidth_estimator, carrier_config and modem. -->
<string name="config_bandwidthEstimateSource">bandwidth_estimator</string>
+ <!-- Whether force to enable telephony new data stack or not -->
+ <bool name="config_force_enable_telephony_new_data_stack">false</bool>
+
<!-- Whether WiFi display is supported by this device.
There are many prerequisites for this feature to work correctly.
Here are a few of them:
@@ -2884,6 +2897,11 @@
<string name="config_sensorUseStartedActivity" translatable="false"
>com.android.systemui/com.android.systemui.sensorprivacy.SensorUseStartedActivity</string>
+ <!-- Component name of the activity used to ask a user to confirm system language change after
+ receiving <Set Menu Language> CEC message. -->
+ <string name="config_hdmiCecSetMenuLanguageActivity"
+ >com.android.systemui/com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity</string>
+
<!-- Name of the dialog that is used to request the user's consent for a Platform VPN -->
<string name="config_platformVpnConfirmDialogComponent" translatable="false"
>com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog</string>
@@ -4161,6 +4179,15 @@
<string name="config_defaultMusicRecognitionService" translatable="false"></string>
+ <!-- The package name for the system's wallpaper effects generation service.
+ This service returns wallpaper effects results.
+ This service must be trusted, as it can be activated without explicit consent of the user.
+ If no service with the specified name exists on the device, wallpaper effects
+ generation service will be disabled.
+ Example: "com.android.intelligence/.WallpaperEffectsGenerationService"
+-->
+ <string name="config_defaultWallpaperEffectsGenerationService" translatable="false"></string>
+
<!-- The package name for the default retail demo app.
This package must be trusted, as it has the permissions to query the usage stats on the
device.
@@ -4971,6 +4998,10 @@
<!-- URI used for Nearby Share SliceProvider scanning. -->
<string translatable="false" name="config_defaultNearbySharingSliceUri"></string>
+ <!-- Component name that accepts settings intents for saved devices.
+ Used by FastPairSettingsFragment. -->
+ <string translatable="false" name="config_defaultNearbyFastPairSettingsDevicesComponent"></string>
+
<!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing
check after reboot or airplane mode toggling -->
<bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">false</bool>
@@ -5133,6 +5164,9 @@
If given value is outside of this range, the option 1 (center) is assummed. -->
<integer name="config_letterboxDefaultPositionForReachability">1</integer>
+ <!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. -->
+ <bool name="config_letterboxIsEducationEnabled">false</bool>
+
<!-- Whether a camera compat controller is enabled to allow the user to apply or revert
treatment for stretched issues in camera viewfinder. -->
<bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
@@ -5248,6 +5282,12 @@
<bool name="config_cecTvSendStandbyOnSleepDisabled_allowed">true</bool>
<bool name="config_cecTvSendStandbyOnSleepDisabled_default">false</bool>
+ <bool name="config_cecSetMenuLanguage_userConfigurable">true</bool>
+ <bool name="config_cecSetMenuLanguageEnabled_allowed">true</bool>
+ <bool name="config_cecSetMenuLanguageEnabled_default">true</bool>
+ <bool name="config_cecSetMenuLanguageDisabled_allowed">true</bool>
+ <bool name="config_cecSetMenuLanguageDisabled_default">false</bool>
+
<bool name="config_cecRcProfileTv_userConfigurable">true</bool>
<bool name="config_cecRcProfileTvNone_allowed">true</bool>
<bool name="config_cecRcProfileTvNone_default">true</bool>
@@ -5634,4 +5674,13 @@
<!-- The amount of time after becoming non-interactive (in ms) after which
Low Power Standby can activate. -->
<integer name="config_lowPowerStandbyNonInteractiveTimeout">5000</integer>
+
+
+ <!-- Mapping to select an Intent.EXTRA_DOCK_STATE value from extcon state
+ key-value pairs. Each entry is evaluated in order and is of the form:
+ "[EXTRA_DOCK_STATE value],key1=value1,key2=value2[,...]"
+ An entry with no key-value pairs is valid and can be used as a wildcard.
+ -->
+ <string-array name="config_dockExtconStateMapping">
+ </string-array>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2e96c65..d57f5ba 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3255,11 +3255,24 @@
<public name="showClockAndComplications" />
<!-- @hide @SystemApi -->
<public name="gameSessionService" />
+ <public name="supportsBatteryGameMode" />
+ <public name="supportsPerformanceGameMode" />
+ <public name="allowGameAngleDriver" />
+ <public name="allowGameDownscaling" />
+ <public name="allowGameFpsOverride" />
<public name="localeConfig" />
<public name="showBackground" />
<public name="inheritKeyStoreKeys" />
<public name="preferKeepClear" />
<public name="autoHandwritingEnabled" />
+ <public name="fromExtendLeft" />
+ <public name="fromExtendTop" />
+ <public name="fromExtendRight" />
+ <public name="fromExtendBottom" />
+ <public name="toExtendLeft" />
+ <public name="toExtendTop" />
+ <public name="toExtendRight" />
+ <public name="toExtendBottom" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01de0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 49a12d1..52c6205 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -880,16 +880,6 @@
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgroupdesc_storage">access photos, media, and files on your device</string>
- <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
- <string name="permgrouplab_readMediaAural">Music & other audio</string>
- <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
- <string name="permgroupdesc_readMediaAural">access audio files on your device</string>
-
- <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
- <string name="permgrouplab_readMediaVisual">Photos & videos</string>
- <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
- <string name="permgroupdesc_readMediaVisual">access images and video files on your device</string>
-
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgrouplab_microphone">Microphone</string>
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1903,21 +1893,6 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
<string name="permdesc_sdcardRead">Allows the app to read the contents of your shared storage.</string>
- <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
- <string name="permlab_readMediaAudio">read audio files from shared storage</string>
- <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
- <string name="permdesc_readMediaAudio">Allows the app to read audio files from your shared storage.</string>
-
- <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
- <string name="permlab_readMediaVideo">read video files from shared storage</string>
- <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
- <string name="permdesc_readMediaVideo">Allows the app to read video files from your shared storage.</string>
-
- <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
- <string name="permlab_readMediaImage">read image files from shared storage</string>
- <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
- <string name="permdesc_readMediaImage">Allows the app to read image files from your shared storage.</string>
-
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
<string name="permlab_sdcardWrite">modify or delete the contents of your shared storage</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
@@ -5882,6 +5857,8 @@
<string name="accessibility_system_action_lock_screen_label">Lock Screen</string>
<!-- Label for taking screenshot action [CHAR LIMIT=NONE] -->
<string name="accessibility_system_action_screenshot_label">Screenshot</string>
+ <!-- Label for headset hook action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_headset_hook_label">Headset Hook</string>
<!-- Label for triggering on-screen accessibility shortcut action [CHAR LIMIT=NONE] -->
<string name="accessibility_system_action_on_screen_a11y_shortcut_label">On-screen Accessibility Shortcut</string>
<!-- Label for showing on-screen accessibility shortcut chooser action [CHAR LIMIT=NONE] -->
@@ -5890,6 +5867,16 @@
<string name="accessibility_system_action_hardware_a11y_shortcut_label">Accessibility Shortcut</string>
<!-- Label for dismissing the notification shade [CHAR LIMIT=NONE] -->
<string name="accessibility_system_action_dismiss_notification_shade">Dismiss Notification Shade</string>
+ <!-- Label for Dpad up action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_dpad_up_label">Dpad Up</string>
+ <!-- Label for Dpad down action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_dpad_down_label">Dpad Down</string>
+ <!-- Label for Dpad left action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_dpad_left_label">Dpad Left</string>
+ <!-- Label for Dpad right action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_dpad_right_label">Dpad Right</string>
+ <!-- Label for Dpad center action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_dpad_center_label">Dpad Center</string>
<!-- Accessibility description of caption view -->
<string name="accessibility_freeform_caption">Caption bar of <xliff:g id="app_name">%1$s</xliff:g>.</string>
@@ -5949,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 20c5c61..facfdb2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -375,6 +375,7 @@
<java-symbol type="string" name="config_usbConfirmActivity" />
<java-symbol type="string" name="config_usbResolverActivity" />
<java-symbol type="string" name="config_sensorUseStartedActivity" />
+ <java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" />
@@ -465,6 +466,7 @@
<java-symbol type="integer" name="config_multiuserMaximumUsers" />
<java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
<java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
+ <java-symbol type="bool" name="config_enableTimeoutToUserZeroWhenDocked" />
<java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
<java-symbol type="xml" name="config_user_types" />
<java-symbol type="integer" name="config_safe_media_volume_index" />
@@ -472,6 +474,7 @@
<java-symbol type="integer" name="config_mobile_mtu" />
<java-symbol type="array" name="config_mobile_tcp_buffers" />
<java-symbol type="string" name="config_tcp_buffers" />
+ <java-symbol type="bool" name="config_force_enable_telephony_new_data_stack" />
<java-symbol type="integer" name="config_volte_replacement_rat"/>
<java-symbol type="integer" name="config_valid_wappush_index" />
<java-symbol type="integer" name="config_overrideHasPermanentMenuKey" />
@@ -1596,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" />
@@ -1694,7 +1704,13 @@
<java-symbol type="anim" name="activity_translucent_open_enter" />
<java-symbol type="anim" name="activity_translucent_close_exit" />
<java-symbol type="anim" name="activity_open_enter" />
+ <java-symbol type="anim" name="activity_open_exit" />
+ <java-symbol type="anim" name="activity_close_enter" />
<java-symbol type="anim" name="activity_close_exit" />
+ <java-symbol type="anim" name="activity_open_enter_legacy" />
+ <java-symbol type="anim" name="activity_open_exit_legacy" />
+ <java-symbol type="anim" name="activity_close_enter_legacy" />
+ <java-symbol type="anim" name="activity_close_exit_legacy" />
<java-symbol type="anim" name="task_fragment_close_enter" />
<java-symbol type="anim" name="task_fragment_close_exit" />
<java-symbol type="anim" name="task_fragment_open_enter" />
@@ -2212,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" />
@@ -2704,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" />
@@ -3670,6 +3689,7 @@
<java-symbol type="string" name="config_defaultContentSuggestionsService" />
<java-symbol type="string" name="config_defaultSearchUiService" />
<java-symbol type="string" name="config_defaultSmartspaceService" />
+ <java-symbol type="string" name="config_defaultWallpaperEffectsGenerationService" />
<java-symbol type="string" name="config_defaultMusicRecognitionService" />
<java-symbol type="string" name="config_defaultAttentionService" />
<java-symbol type="string" name="config_defaultRotationResolverService" />
@@ -4098,6 +4118,7 @@
<java-symbol type="layout" name="chooser_action_button" />
<java-symbol type="dimen" name="chooser_action_button_icon_size" />
<java-symbol type="string" name="config_defaultNearbySharingComponent" />
+ <java-symbol type="string" name="config_defaultNearbyFastPairSettingsDevicesComponent" />
<java-symbol type="bool" name="config_disable_all_cb_messages" />
<java-symbol type="drawable" name="ic_close" />
@@ -4125,10 +4146,16 @@
<java-symbol type="string" name="accessibility_system_action_quick_settings_label" />
<java-symbol type="string" name="accessibility_system_action_recents_label" />
<java-symbol type="string" name="accessibility_system_action_screenshot_label" />
+ <java-symbol type="string" name="accessibility_system_action_headset_hook_label" />
<java-symbol type="string" name="accessibility_system_action_on_screen_a11y_shortcut_label" />
<java-symbol type="string" name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" />
<java-symbol type="string" name="accessibility_system_action_hardware_a11y_shortcut_label" />
<java-symbol type="string" name="accessibility_system_action_dismiss_notification_shade" />
+ <java-symbol type="string" name="accessibility_system_action_dpad_up_label" />
+ <java-symbol type="string" name="accessibility_system_action_dpad_down_label" />
+ <java-symbol type="string" name="accessibility_system_action_dpad_left_label" />
+ <java-symbol type="string" name="accessibility_system_action_dpad_right_label" />
+ <java-symbol type="string" name="accessibility_system_action_dpad_center_label" />
<java-symbol type="string" name="accessibility_freeform_caption" />
@@ -4328,6 +4355,7 @@
<java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
<java-symbol type="bool" name="config_letterboxIsReachabilityEnabled" />
<java-symbol type="integer" name="config_letterboxDefaultPositionForReachability" />
+ <java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
<java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
@@ -4448,6 +4476,12 @@
<java-symbol type="bool" name="config_cecTvSendStandbyOnSleepDisabled_allowed" />
<java-symbol type="bool" name="config_cecTvSendStandbyOnSleepDisabled_default" />
+ <java-symbol type="bool" name="config_cecSetMenuLanguage_userConfigurable" />
+ <java-symbol type="bool" name="config_cecSetMenuLanguageEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecSetMenuLanguageEnabled_default" />
+ <java-symbol type="bool" name="config_cecSetMenuLanguageDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecSetMenuLanguageDisabled_default" />
+
<java-symbol type="bool" name="config_cecRcProfileTv_userConfigurable" />
<java-symbol type="bool" name="config_cecRcProfileTvNone_allowed" />
<java-symbol type="bool" name="config_cecRcProfileTvNone_default" />
@@ -4675,6 +4709,8 @@
<java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
+ <java-symbol type="array" name="config_dockExtconStateMapping" />
+
<java-symbol type="string" name="notification_channel_abusive_bg_apps"/>
<java-symbol type="string" name="notification_title_abusive_bg_apps"/>
<java-symbol type="string" name="notification_content_abusive_bg_apps"/>
diff --git a/core/tests/coretests/res/layout/remote_view_relative_layout.xml b/core/tests/coretests/res/layout/remote_view_relative_layout.xml
new file mode 100644
index 0000000..713a4c8
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_relative_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml
new file mode 100644
index 0000000..74c939b
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/themed_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:theme="@style/RelativeLayoutAlignTop25Alpha"/>
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/core/tests/coretests/res/layout/remote_views_light_background_text.xml
similarity index 62%
copy from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
copy to core/tests/coretests/res/layout/remote_views_light_background_text.xml
index dd818a0..f300f09 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/core/tests/coretests/res/layout/remote_views_light_background_text.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2020 The Android Open Source Project
+ ~ Copyright (C) 2016 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.
@@ -12,11 +12,10 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ ~ limitations under the License
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <gradient
- android:angle="90"
- android:startColor="@color/screenshot_background_protection_start"
- android:endColor="#00000000"/>
-</shape>
\ No newline at end of file
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/light_background_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/core/tests/coretests/res/layout/remote_views_list.xml
similarity index 62%
copy from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
copy to core/tests/coretests/res/layout/remote_views_list.xml
index dd818a0..ca43bc8 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/core/tests/coretests/res/layout/remote_views_list.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2020 The Android Open Source Project
+ ~ Copyright (C) 2016 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.
@@ -12,11 +12,10 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ ~ limitations under the License
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <gradient
- android:angle="90"
- android:startColor="@color/screenshot_background_protection_start"
- android:endColor="#00000000"/>
-</shape>
\ No newline at end of file
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml
index 352b4dc..32eebb35 100644
--- a/core/tests/coretests/res/values/styles.xml
+++ b/core/tests/coretests/res/values/styles.xml
@@ -34,6 +34,16 @@
<style name="LayoutInDisplayCutoutModeAlways">
<item name="android:windowLayoutInDisplayCutoutMode">always</item>
</style>
+ <style name="RelativeLayoutAlignBottom50Alpha">
+ <item name="android:layout_alignParentTop">false</item>
+ <item name="android:layout_alignParentBottom">true</item>
+ <item name="android:alpha">0.5</item>
+ </style>
+ <style name="RelativeLayoutAlignTop25Alpha">
+ <item name="android:layout_alignParentTop">true</item>
+ <item name="android:layout_alignParentBottom">false</item>
+ <item name="android:alpha">0.25</item>
+ </style>
<style name="WindowBackgroundColorLiteral">
<item name="android:windowBackground">#00FF00</item>
</style>
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index d3e8bb0..5338d04 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -37,9 +37,9 @@
@SmallTest
public class PropertyInvalidatedCacheTests {
- // This property is never set. The test process does not have permission to set any
- // properties.
- static final String CACHE_PROPERTY = "cache_key.cache_test_a";
+ // Configuration for creating caches
+ private static final int MODULE = PropertyInvalidatedCache.MODULE_TEST;
+ private static final String API = "testApi";
// This class is a proxy for binder calls. It contains a counter that increments
// every time the class is queried.
@@ -64,6 +64,25 @@
}
}
+ // The functions for querying the server.
+ private static class ServerQuery
+ extends PropertyInvalidatedCache.QueryHandler<Integer, Boolean> {
+ private final ServerProxy mServer;
+
+ ServerQuery(ServerProxy server) {
+ mServer = server;
+ }
+
+ @Override
+ public Boolean apply(Integer x) {
+ return mServer.query(x);
+ }
+ @Override
+ public boolean shouldBypassCache(Integer x) {
+ return x % 13 == 0;
+ }
+ }
+
// Clear the test mode after every test, in case this process is used for other
// tests. This also resets the test property map.
@After
@@ -82,19 +101,11 @@
// Create a cache that uses simple arithmetic to computer its values.
PropertyInvalidatedCache<Integer, Boolean> testCache =
- new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- @Override
- public boolean bypass(Integer x) {
- return x % 13 == 0;
- }
- };
+ new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
PropertyInvalidatedCache.setTestMode(true);
- PropertyInvalidatedCache.testPropertyName(CACHE_PROPERTY);
+ testCache.testPropertyName();
tester.verify(0);
assertEquals(tester.value(3), testCache.query(3));
@@ -136,26 +147,14 @@
// Three caches, all using the same system property but one uses a different name.
PropertyInvalidatedCache<Integer, Boolean> cache1 =
- new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
PropertyInvalidatedCache<Integer, Boolean> cache2 =
- new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
PropertyInvalidatedCache<Integer, Boolean> cache3 =
- new PropertyInvalidatedCache<>(4, CACHE_PROPERTY, "cache3") {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ new PropertyInvalidatedCache<>(4, MODULE, API, "cache3",
+ new ServerQuery(tester));
// Caches are enabled upon creation.
assertEquals(false, cache1.getDisabledState());
@@ -176,45 +175,29 @@
assertEquals(false, cache3.getDisabledState());
// Create a new cache1. Verify that the new instance is disabled.
- cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
assertEquals(true, cache1.getDisabledState());
// Remove the record of caches being locally disabled. This is a clean-up step.
- cache1.clearDisableLocal();
+ cache1.forgetDisableLocal();
assertEquals(true, cache1.getDisabledState());
assertEquals(true, cache2.getDisabledState());
assertEquals(false, cache3.getDisabledState());
// Create a new cache1. Verify that the new instance is not disabled.
- cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
assertEquals(false, cache1.getDisabledState());
}
- private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";
+ private static class TestQuery
+ extends PropertyInvalidatedCache.QueryHandler<Integer, String> {
- private static class TestCache extends PropertyInvalidatedCache<Integer, String> {
- TestCache() {
- this(CACHE_PROPERTY);
- }
-
- TestCache(String key) {
- super(4, key);
- setTestMode(true);
- testPropertyName(key);
- }
+ private int mRecomputeCount = 0;
@Override
- public String recompute(Integer qv) {
+ public String apply(Integer qv) {
mRecomputeCount += 1;
return "foo" + qv.toString();
}
@@ -222,15 +205,40 @@
int getRecomputeCount() {
return mRecomputeCount;
}
+ }
- private int mRecomputeCount = 0;
+ private static class TestCache extends PropertyInvalidatedCache<Integer, String> {
+ private final TestQuery mQuery;
+
+ TestCache() {
+ this(MODULE, API);
+ }
+
+ TestCache(int module, String api) {
+ this(module, api, new TestQuery());
+ setTestMode(true);
+ testPropertyName();
+ }
+
+ TestCache(int module, String api, TestQuery query) {
+ super(4, module, api, api, query);
+ mQuery = query;
+ setTestMode(true);
+ testPropertyName();
+ }
+
+ public int getRecomputeCount() {
+ return mQuery.getRecomputeCount();
+ }
+
+
}
@Test
public void testCacheRecompute() {
TestCache cache = new TestCache();
cache.invalidateCache();
- assertEquals(cache.isDisabledLocal(), false);
+ assertEquals(cache.isDisabled(), false);
assertEquals("foo5", cache.query(5));
assertEquals(1, cache.getRecomputeCount());
assertEquals("foo5", cache.query(5));
@@ -241,6 +249,11 @@
assertEquals("foo5", cache.query(5));
assertEquals("foo5", cache.query(5));
assertEquals(3, cache.getRecomputeCount());
+ // Invalidate the cache with a direct call to the property.
+ PropertyInvalidatedCache.invalidateCache(MODULE, API);
+ assertEquals("foo5", cache.query(5));
+ assertEquals("foo5", cache.query(5));
+ assertEquals(4, cache.getRecomputeCount());
}
@Test
@@ -257,7 +270,8 @@
@Test
public void testCachePropertyUnset() {
- TestCache cache = new TestCache(UNSET_KEY);
+ final String UNSET_API = "otherApi";
+ TestCache cache = new TestCache(MODULE, UNSET_API);
assertEquals("foo5", cache.query(5));
assertEquals("foo5", cache.query(5));
assertEquals(2, cache.getRecomputeCount());
@@ -327,17 +341,40 @@
@Test
public void testLocalProcessDisable() {
TestCache cache = new TestCache();
- assertEquals(cache.isDisabledLocal(), false);
+ assertEquals(cache.isDisabled(), false);
cache.invalidateCache();
assertEquals("foo5", cache.query(5));
assertEquals(1, cache.getRecomputeCount());
assertEquals("foo5", cache.query(5));
assertEquals(1, cache.getRecomputeCount());
- assertEquals(cache.isDisabledLocal(), false);
+ assertEquals(cache.isDisabled(), false);
cache.disableLocal();
- assertEquals(cache.isDisabledLocal(), true);
+ assertEquals(cache.isDisabled(), true);
assertEquals("foo5", cache.query(5));
assertEquals("foo5", cache.query(5));
assertEquals(3, cache.getRecomputeCount());
}
+
+ @Test
+ public void testPropertyNames() {
+ String n1;
+ n1 = PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "getPackageInfo");
+ assertEquals(n1, "cache_key.system_server.get_package_info");
+ n1 = PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "get_package_info");
+ assertEquals(n1, "cache_key.system_server.get_package_info");
+ try {
+ n1 = PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM - 1, "get package_info");
+ // n1 is an invalid api name.
+ assertEquals(false, true);
+ } catch (IllegalArgumentException e) {
+ // An exception is expected here.
+ }
+
+ n1 = PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState");
+ assertEquals(n1, "cache_key.bluetooth.get_state");
+ }
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index b2c4274..5c9044c 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -646,6 +646,10 @@
}
@Override
+ public void dumpResources(ParcelFileDescriptor fd, RemoteCallback finishCallback) {
+ }
+
+ @Override
public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
}
diff --git a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
index b66642c..fa657f7 100644
--- a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
+++ b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
@@ -71,6 +71,8 @@
private Resources mResources;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Drawable mDrawable;
+ @Mock
+ private PackageManager mPackageManager;
private CrossProfileApps mCrossProfileApps;
@Before
@@ -87,6 +89,7 @@
Context.DEVICE_POLICY_SERVICE);
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
mDevicePolicyManager);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
}
@Before
@@ -131,7 +134,8 @@
setValidTargetProfile(MANAGED_PROFILE);
mCrossProfileApps.getProfileSwitchingIconDrawable(MANAGED_PROFILE);
- verify(mResources).getDrawable(R.drawable.ic_corp_badge, null);
+ verify(mPackageManager).getUserBadgeForDensityNoBackground(
+ MANAGED_PROFILE, /* density= */0);
}
@Test
diff --git a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java b/core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java
similarity index 85%
rename from core/tests/coretests/src/android/content/pm/PackageHelperTests.java
rename to core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java
index 947da0b..0629a99 100644
--- a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
+++ b/core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java
@@ -25,7 +25,7 @@
import android.test.AndroidTestCase;
import android.util.Log;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import org.mockito.Mockito;
@@ -36,10 +36,9 @@
import java.util.UUID;
@Presubmit
-public class PackageHelperTests extends AndroidTestCase {
+public class InstallLocationUtilsTests extends AndroidTestCase {
private static final boolean localLOGV = true;
public static final String TAG = "PackageHelperTests";
- protected final String PREFIX = "android.content.pm";
private static final String sInternalVolPath = "/data";
private static final String sAdoptedVolPath = "/mnt/expand/123";
@@ -88,11 +87,14 @@
UUID internalUuid = UUID.randomUUID();
UUID adoptedUuid = UUID.randomUUID();
UUID publicUuid = UUID.randomUUID();
- Mockito.when(storageManager.getStorageBytesUntilLow(internalFile)).thenReturn(sInternalSize);
+ Mockito.when(storageManager.getStorageBytesUntilLow(internalFile))
+ .thenReturn(sInternalSize);
Mockito.when(storageManager.getStorageBytesUntilLow(adoptedFile)).thenReturn(sAdoptedSize);
Mockito.when(storageManager.getStorageBytesUntilLow(publicFile)).thenReturn(sPublicSize);
- Mockito.when(storageManager.getUuidForPath(Mockito.eq(internalFile))).thenReturn(internalUuid);
- Mockito.when(storageManager.getUuidForPath(Mockito.eq(adoptedFile))).thenReturn(adoptedUuid);
+ Mockito.when(storageManager.getUuidForPath(Mockito.eq(internalFile)))
+ .thenReturn(internalUuid);
+ Mockito.when(storageManager.getUuidForPath(Mockito.eq(adoptedFile)))
+ .thenReturn(adoptedUuid);
Mockito.when(storageManager.getUuidForPath(Mockito.eq(publicFile))).thenReturn(publicUuid);
Mockito.when(storageManager.getAllocatableBytes(Mockito.eq(internalUuid), Mockito.anyInt()))
.thenReturn(sInternalSize);
@@ -103,7 +105,7 @@
return storageManager;
}
- private static final class MockedInterface extends PackageHelper.TestableInterface {
+ private static final class MockedInterface extends InstallLocationUtils.TestableInterface {
private boolean mForceAllowOnExternal = false;
private boolean mAllow3rdPartyOnInternal = true;
private ApplicationInfo mApplicationInfo = null;
@@ -164,25 +166,25 @@
mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
String volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
@@ -192,7 +194,7 @@
mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
} catch(IOException e) {
@@ -202,7 +204,7 @@
mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
} catch(IOException e) {
@@ -212,7 +214,7 @@
mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
} catch(IOException e) {
@@ -222,7 +224,7 @@
mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
} catch(IOException e) {
@@ -240,13 +242,13 @@
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
String volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sInternalVolUuid, volume);
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sInternalVolUuid, volume);
}
@@ -260,25 +262,25 @@
appInfo.volumeUuid = sAdoptedVolUuid;
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
}
@@ -292,7 +294,7 @@
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
fail("Expected exception was not thrown " + appInfo.volumeUuid);
} catch (IOException e) {
@@ -302,7 +304,7 @@
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
fail("Expected exception was not thrown " + appInfo.volumeUuid);
} catch (IOException e) {
@@ -312,7 +314,7 @@
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
fail("Expected exception was not thrown " + appInfo.volumeUuid);
} catch (IOException e) {
@@ -322,7 +324,7 @@
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
fail("Expected exception was not thrown " + appInfo.volumeUuid);
} catch (IOException e) {
@@ -336,28 +338,28 @@
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
String volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
// Should return the volume with bigger available space.
assertEquals(sInternalVolUuid, volume);
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
// Should return the volume with bigger available space.
assertEquals(sInternalVolUuid, volume);
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
// Should return the volume with bigger available space.
assertEquals(sAdoptedVolUuid, volume);
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
// Should return the volume with bigger available space.
assertEquals(sAdoptedVolUuid, volume);
@@ -371,20 +373,20 @@
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
String volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sInternalVolUuid, volume);
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sInternalVolUuid, volume);
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
try {
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
} catch (IOException e) {
@@ -395,7 +397,7 @@
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
}
@@ -407,7 +409,7 @@
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
String volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
// Should return the non-internal volume.
assertEquals(sAdoptedVolUuid, volume);
@@ -415,7 +417,7 @@
appInfo = null;
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
// Should return the non-internal volume.
assertEquals(sAdoptedVolUuid, volume);
@@ -428,7 +430,7 @@
true /*allow 3rd party on internal*/);
String volume = null;
try {
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal ONLY*/,
1000000 /*size too big*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
@@ -445,7 +447,7 @@
false /*allow 3rd party on internal*/);
String volume = null;
try {
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
} catch (IOException e) {
@@ -456,7 +458,7 @@
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
@@ -474,7 +476,7 @@
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
String volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
}
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index 104f077..f7ca822 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -146,6 +146,7 @@
@Test
public void testValidateWaveformBuilder() {
+ // Cover builder methods
VibrationEffect.startWaveform(targetAmplitude(1))
.addTransition(Duration.ofSeconds(1), targetAmplitude(0.5f), targetFrequency(100))
.addTransition(Duration.ZERO, targetAmplitude(0f), targetFrequency(200))
@@ -158,6 +159,39 @@
.build()
.validate();
+ // Make sure class summary javadoc examples compile and are valid.
+ // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE WaveformBuilder javadocs.
+ VibrationEffect.startWaveform(targetFrequency(60))
+ .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+ .addSustain(Duration.ofMillis(200))
+ .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+ .build()
+ .validate();
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addOffDuration(Duration.ofMillis(20))
+ .repeatEffectIndefinitely(
+ VibrationEffect.startWaveform(targetAmplitude(0.2f))
+ .addSustain(Duration.ofMillis(10))
+ .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f))
+ .addSustain(Duration.ofMillis(30))
+ .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f))
+ .addSustain(Duration.ofMillis(50))
+ .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f))
+ .build())
+ .compose()
+ .validate();
+ VibrationEffect.createWaveform(new long[]{10, 20, 30}, new int[]{51, 102, 204}, -1)
+ .validate();
+ VibrationEffect.startWaveform(targetAmplitude(0.2f))
+ .addSustain(Duration.ofMillis(10))
+ .addTransition(Duration.ZERO, targetAmplitude(0.4f))
+ .addSustain(Duration.ofMillis(20))
+ .addTransition(Duration.ZERO, targetAmplitude(0.8f))
+ .addSustain(Duration.ofMillis(30))
+ .build()
+ .validate();
+
assertThrows(IllegalStateException.class,
() -> VibrationEffect.startWaveform().build().validate());
assertThrows(IllegalArgumentException.class, () -> targetAmplitude(-2));
@@ -171,6 +205,7 @@
@Test
public void testValidateComposed() {
+ // Cover builder methods
VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.addEffect(TEST_ONE_SHOT)
@@ -178,11 +213,28 @@
.addOffDuration(Duration.ofMillis(100))
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0))
+ .compose()
+ .validate();
+ VibrationEffect.startComposition()
+ .repeatEffectIndefinitely(TEST_ONE_SHOT)
.compose()
.validate();
+ // Make sure class summary javadoc examples compile and are valid.
+ // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE Composition javadocs.
VibrationEffect.startComposition()
- .repeatEffectIndefinitely(TEST_ONE_SHOT)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100)
+ .compose()
+ .validate();
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
+ .addOffDuration(Duration.ofMillis(10))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK))
+ .addOffDuration(Duration.ofMillis(50))
+ .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0))
.compose()
.validate();
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 2018836..99670d9 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -58,7 +58,7 @@
// The number of flags held in boolean properties. Their values should also be double-checked
// in the methods above.
- private static final int NUM_BOOLEAN_PROPERTIES = 23;
+ private static final int NUM_BOOLEAN_PROPERTIES = 24;
@Test
public void testStandardActions_serializationFlagIsValid() {
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 059c764..00b3693 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -16,8 +16,12 @@
package android.widget;
+import static com.android.internal.R.id.pending_intent_tag;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@@ -31,7 +35,10 @@
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Binder;
+import android.os.Looper;
import android.os.Parcel;
+import android.util.SizeF;
+import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.ViewGroup;
@@ -49,6 +56,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Map;
import java.util.concurrent.CountDownLatch;
/**
@@ -261,6 +269,148 @@
verifyViewTree(syncView, asyncView, "row1-c1", "row1-c2", "row1-c3", "row2-c1", "row2-c2");
}
+ @Test
+ public void nestedViews_setRemoteAdapter_intent() {
+ Looper.prepare();
+
+ AppWidgetHostView widget = new AppWidgetHostView(mContext);
+ RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host);
+ RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host);
+ RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list);
+ inner2.setRemoteAdapter(R.id.list, new Intent());
+ inner1.addView(R.id.container, inner2);
+ top.addView(R.id.container, inner1);
+
+ View view = top.apply(mContext, widget);
+ widget.addView(view);
+
+ ListView listView = (ListView) view.findViewById(R.id.list);
+ listView.onRemoteAdapterConnected();
+ assertNotNull(listView.getAdapter());
+
+ top.reapply(mContext, view);
+ listView = (ListView) view.findViewById(R.id.list);
+ assertNotNull(listView.getAdapter());
+ }
+
+ @Test
+ public void nestedViews_setRemoteAdapter_remoteCollectionItems() {
+ AppWidgetHostView widget = new AppWidgetHostView(mContext);
+ RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host);
+ RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host);
+ RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list);
+ inner2.setRemoteAdapter(
+ R.id.list,
+ new RemoteViews.RemoteCollectionItems.Builder()
+ .addItem(0, new RemoteViews(mPackage, R.layout.remote_view_host))
+ .build());
+ inner1.addView(R.id.container, inner2);
+ top.addView(R.id.container, inner1);
+
+ View view = top.apply(mContext, widget);
+ widget.addView(view);
+
+ ListView listView = (ListView) view.findViewById(R.id.list);
+ assertNotNull(listView.getAdapter());
+
+ top.reapply(mContext, view);
+ listView = (ListView) view.findViewById(R.id.list);
+ assertNotNull(listView.getAdapter());
+ }
+
+ @Test
+ public void nestedViews_collectionChildFlag() throws Exception {
+ RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text);
+ nested.setOnClickPendingIntent(
+ R.id.text,
+ PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ );
+
+ RemoteViews listItem = new RemoteViews(mPackage, R.layout.remote_view_host);
+ listItem.addView(R.id.container, nested);
+ listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+ View view = listItem.apply(mContext, mContainer);
+ TextView text = (TextView) view.findViewById(R.id.text);
+ assertNull(text.getTag(pending_intent_tag));
+ }
+
+ @Test
+ public void landscapePortraitViews_collectionChildFlag() throws Exception {
+ RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+ inner.setOnClickPendingIntent(
+ R.id.text,
+ PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ );
+
+ RemoteViews listItem = new RemoteViews(inner, inner);
+ listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+ View view = listItem.apply(mContext, mContainer);
+ TextView text = (TextView) view.findViewById(R.id.text);
+ assertNull(text.getTag(pending_intent_tag));
+ }
+
+ @Test
+ public void sizedViews_collectionChildFlag() throws Exception {
+ RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+ inner.setOnClickPendingIntent(
+ R.id.text,
+ PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ );
+
+ RemoteViews listItem = new RemoteViews(
+ Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner));
+ listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+ View view = listItem.apply(mContext, mContainer);
+ TextView text = (TextView) view.findViewById(R.id.text);
+ assertNull(text.getTag(pending_intent_tag));
+ }
+
+ @Test
+ public void nestedViews_lightBackgroundLayoutFlag() {
+ RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text);
+ nested.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+ RemoteViews parent = new RemoteViews(mPackage, R.layout.remote_view_host);
+ parent.addView(R.id.container, nested);
+ parent.setLightBackgroundLayoutId(R.layout.remote_view_host);
+ parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+ View view = parent.apply(mContext, mContainer);
+ assertNull(view.findViewById(R.id.text));
+ assertNotNull(view.findViewById(R.id.light_background_text));
+ }
+
+
+ @Test
+ public void landscapePortraitViews_lightBackgroundLayoutFlag() {
+ RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+ inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+ RemoteViews parent = new RemoteViews(inner, inner);
+ parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+ View view = parent.apply(mContext, mContainer);
+ assertNull(view.findViewById(R.id.text));
+ assertNotNull(view.findViewById(R.id.light_background_text));
+ }
+
+ @Test
+ public void sizedViews_lightBackgroundLayoutFlag() {
+ RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+ inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+ RemoteViews parent = new RemoteViews(
+ Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner));
+ parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+ View view = parent.apply(mContext, mContainer);
+ assertNull(view.findViewById(R.id.text));
+ assertNotNull(view.findViewById(R.id.light_background_text));
+ }
+
private RemoteViews createViewChained(int depth, String... texts) {
RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host);
@@ -483,6 +633,47 @@
index, inflated.getTag(com.android.internal.R.id.notification_action_index_tag));
}
+ @Test
+ public void nestedViews_themesPropagateCorrectly() {
+ Context themedContext =
+ new ContextThemeWrapper(mContext, R.style.RelativeLayoutAlignBottom50Alpha);
+ RelativeLayout rootParent = new RelativeLayout(themedContext);
+
+ RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_relative_layout);
+ RemoteViews inner1 =
+ new RemoteViews(mPackage, R.layout.remote_view_relative_layout_with_theme);
+ RemoteViews inner2 =
+ new RemoteViews(mPackage, R.layout.remote_view_relative_layout);
+
+ inner1.addView(R.id.themed_layout, inner2);
+ top.addView(R.id.container, inner1);
+
+ RelativeLayout root = (RelativeLayout) top.apply(themedContext, rootParent);
+ assertEquals(0.5, root.getAlpha(), 0.);
+ RelativeLayout.LayoutParams rootParams =
+ (RelativeLayout.LayoutParams) root.getLayoutParams();
+ assertEquals(RelativeLayout.TRUE,
+ rootParams.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM));
+
+ // The theme is set on inner1View and its descendants. However, inner1View does
+ // not get its layout params from its theme (though its descendants do), but other
+ // attributes such as alpha are set.
+ RelativeLayout inner1View = (RelativeLayout) root.getChildAt(0);
+ assertEquals(R.id.themed_layout, inner1View.getId());
+ assertEquals(0.25, inner1View.getAlpha(), 0.);
+ RelativeLayout.LayoutParams inner1Params =
+ (RelativeLayout.LayoutParams) inner1View.getLayoutParams();
+ assertEquals(RelativeLayout.TRUE,
+ inner1Params.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM));
+
+ RelativeLayout inner2View = (RelativeLayout) inner1View.getChildAt(0);
+ assertEquals(0.25, inner2View.getAlpha(), 0.);
+ RelativeLayout.LayoutParams inner2Params =
+ (RelativeLayout.LayoutParams) inner2View.getLayoutParams();
+ assertEquals(RelativeLayout.TRUE,
+ inner2Params.getRule(RelativeLayout.ALIGN_PARENT_TOP));
+ }
+
private class WidgetContainer extends AppWidgetHostView {
int[] mSharedViewIds;
String[] mSharedViewNames;
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 75d2025..2817728f 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 @@
}
@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/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index b655369..f5cbffb 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -17,6 +17,7 @@
package com.android.internal.os;
import static android.os.BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+import static android.os.BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR;
import static android.os.BatteryStats.STATS_SINCE_CHARGED;
import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
@@ -30,6 +31,12 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.telephony.Annotation;
+import android.telephony.CellSignalStrength;
+import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.view.Display;
@@ -1165,6 +1172,185 @@
"D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
}
+ @SmallTest
+ public void testGetPerStateActiveRadioDurationMs() {
+ final MockClock clock = new MockClock(); // holds realtime and uptime in ms
+ final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
+ final int ratCount = BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT;
+ final int frequencyCount = ServiceState.FREQUENCY_RANGE_MMWAVE + 1;
+ final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
+
+ final long[][][] expectedDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
+ for (int rat = 0; rat < ratCount; rat++) {
+ for (int freq = 0; freq < frequencyCount; freq++) {
+ for (int txLvl = 0; txLvl < txLevelCount; txLvl++) {
+ expectedDurationsMs[rat][freq][txLvl] = 0;
+ }
+ }
+ }
+
+ class ModemAndBatteryState {
+ public long currentTimeMs = 100;
+ public boolean onBattery = false;
+ public boolean modemActive = false;
+ @Annotation.NetworkType
+ public int currentNetworkDataType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ @BatteryStats.RadioAccessTechnology
+ public int currentRat = BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
+ @ServiceState.FrequencyRange
+ public int currentFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
+ public SparseIntArray currentSignalStrengths = new SparseIntArray();
+
+ void setOnBattery(boolean onBattery) {
+ this.onBattery = onBattery;
+ bi.updateTimeBasesLocked(onBattery, Display.STATE_OFF, currentTimeMs * 1000,
+ currentTimeMs * 1000);
+ }
+
+ void setModemActive(boolean active) {
+ modemActive = active;
+ final int state = active ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+ : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+ bi.noteMobileRadioPowerStateLocked(state, currentTimeMs * 1000_000L, UID);
+ }
+
+ void setRatType(@Annotation.NetworkType int dataType,
+ @BatteryStats.RadioAccessTechnology int rat) {
+ currentNetworkDataType = dataType;
+ currentRat = rat;
+ bi.notePhoneDataConnectionStateLocked(dataType, true, ServiceState.STATE_IN_SERVICE,
+ currentFrequencyRange);
+ }
+
+ void setFrequencyRange(@ServiceState.FrequencyRange int frequency) {
+ currentFrequencyRange = frequency;
+ bi.notePhoneDataConnectionStateLocked(currentNetworkDataType, true,
+ ServiceState.STATE_IN_SERVICE, frequency);
+ }
+
+ void setSignalStrength(@BatteryStats.RadioAccessTechnology int rat, int strength) {
+ currentSignalStrengths.put(rat, strength);
+ final int size = currentSignalStrengths.size();
+ final int newestGenSignalStrength = currentSignalStrengths.valueAt(size - 1);
+ bi.notePhoneSignalStrengthLocked(newestGenSignalStrength, currentSignalStrengths);
+ }
+ }
+ final ModemAndBatteryState state = new ModemAndBatteryState();
+
+ IntConsumer incrementTime = inc -> {
+ state.currentTimeMs += inc;
+ clock.realtime = clock.uptime = state.currentTimeMs;
+
+ // If the device is not on battery, no timers should increment.
+ if (!state.onBattery) return;
+ // If the modem is not active, no timers should increment.
+ if (!state.modemActive) return;
+
+ final int currentRat = state.currentRat;
+ final int currentFrequencyRange =
+ currentRat == RADIO_ACCESS_TECHNOLOGY_NR ? state.currentFrequencyRange : 0;
+ int currentSignalStrength = state.currentSignalStrengths.get(currentRat);
+ expectedDurationsMs[currentRat][currentFrequencyRange][currentSignalStrength] += inc;
+ };
+
+ state.setOnBattery(false);
+ state.setModemActive(false);
+ state.setRatType(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER);
+ state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_UNKNOWN);
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // While not on battery, the timers should not increase.
+ state.setModemActive(true);
+ incrementTime.accept(100);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
+ incrementTime.accept(200);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR,
+ CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+ incrementTime.accept(500);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_MMWAVE);
+ incrementTime.accept(300);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setRatType(TelephonyManager.NETWORK_TYPE_LTE,
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE);
+ incrementTime.accept(400);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
+ incrementTime.accept(500);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // When set on battery, currently active state (RAT:LTE, Signal Strength:Moderate) should
+ // start counting up.
+ state.setOnBattery(true);
+ incrementTime.accept(600);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Changing LTE signal strength should be tracked.
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_POOR);
+ incrementTime.accept(700);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ incrementTime.accept(800);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+ incrementTime.accept(900);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_GREAT);
+ incrementTime.accept(1000);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Change in the signal strength of nonactive RAT should not affect anything.
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+ CellSignalStrength.SIGNAL_STRENGTH_POOR);
+ incrementTime.accept(1100);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Changing to OTHER Rat should start tracking the poor signal strength.
+ state.setRatType(TelephonyManager.NETWORK_TYPE_CDMA,
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER);
+ incrementTime.accept(1200);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Noting frequency change should not affect non NR Rat.
+ state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_HIGH);
+ incrementTime.accept(1300);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Now the NR Rat, HIGH frequency range, good signal strength should start counting.
+ state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
+ incrementTime.accept(1400);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Noting frequency change should not affect non NR Rat.
+ state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_LOW);
+ incrementTime.accept(1500);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Modem no longer active, should not be tracking any more.
+ state.setModemActive(false);
+ incrementTime.accept(1500);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ }
+
private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
// Note that noteUidProcessStateLocked uses ActivityManager process states.
if (fgOn) {
@@ -1238,4 +1424,30 @@
bi.getScreenBrightnessTime(bin, currentTimeMs * 1000, STATS_SINCE_CHARGED));
}
}
+
+ private void checkPerStateActiveRadioDurations(long[][][] expectedDurationsMs,
+ BatteryStatsImpl bi, long currentTimeMs) {
+ for (int rat = 0; rat < expectedDurationsMs.length; rat++) {
+ final long[][] expectedRatDurationsMs = expectedDurationsMs[rat];
+ for (int freq = 0; freq < expectedRatDurationsMs.length; freq++) {
+ final long[] expectedFreqDurationsMs = expectedRatDurationsMs[freq];
+ for (int strength = 0; strength < expectedFreqDurationsMs.length; strength++) {
+ final long expectedSignalStrengthDurationMs = expectedFreqDurationsMs[strength];
+ final long actualDurationMs = bi.getActiveRadioDurationMs(rat, freq,
+ strength, currentTimeMs);
+
+ // Build a verbose fail message, just in case.
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Wrong time in state for RAT:");
+ sb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+ sb.append(", frequency:");
+ sb.append(ServiceState.frequencyRangeToString(freq));
+ sb.append(", strength:");
+ sb.append(strength);
+
+ assertEquals(sb.toString(), expectedSignalStrengthDurationMs, actualDurationMs);
+ }
+ }
+ }
+ }
}
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index db63e6e..4c247427 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -38,7 +38,6 @@
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
@@ -155,7 +154,7 @@
verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
Mockito.reset(callback);
- mDeviceStateManagerGlobal.cancelRequest(request);
+ mDeviceStateManagerGlobal.cancelStateRequest();
verify(callback).onStateChanged(eq(mService.getBaseState()));
}
@@ -172,7 +171,7 @@
verify(callback).onRequestActivated(eq(request));
Mockito.reset(callback);
- mDeviceStateManagerGlobal.cancelRequest(request);
+ mDeviceStateManagerGlobal.cancelStateRequest();
verify(callback).onRequestCanceled(eq(request));
}
@@ -203,13 +202,13 @@
private int[] mSupportedStates = new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE };
private int mBaseState = DEFAULT_DEVICE_STATE;
- private ArrayList<Request> mRequests = new ArrayList<>();
+ private Request mRequest;
private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
private DeviceStateInfo getInfo() {
- final int mergedState = mRequests.isEmpty()
- ? mBaseState : mRequests.get(mRequests.size() - 1).state;
+ final int mergedState = mRequest == null
+ ? mBaseState : mRequest.state;
return new DeviceStateInfo(mSupportedStates, mBaseState, mergedState);
}
@@ -245,11 +244,10 @@
@Override
public void requestState(IBinder token, int state, int flags) {
- if (!mRequests.isEmpty()) {
- final Request topRequest = mRequests.get(mRequests.size() - 1);
+ if (mRequest != null) {
for (IDeviceStateManagerCallback callback : mCallbacks) {
try {
- callback.onRequestSuspended(topRequest.token);
+ callback.onRequestCanceled(mRequest.token);
} catch (RemoteException e) {
// Do nothing. Should never happen.
}
@@ -257,7 +255,7 @@
}
final Request request = new Request(token, state, flags);
- mRequests.add(request);
+ mRequest = request;
notifyDeviceStateInfoChanged();
for (IDeviceStateManagerCallback callback : mCallbacks) {
@@ -270,20 +268,9 @@
}
@Override
- public void cancelRequest(IBinder token) {
- int index = -1;
- for (int i = 0; i < mRequests.size(); i++) {
- if (mRequests.get(i).token.equals(token)) {
- index = i;
- break;
- }
- }
-
- if (index == -1) {
- throw new IllegalArgumentException("Unknown request: " + token);
- }
-
- mRequests.remove(index);
+ public void cancelStateRequest() {
+ IBinder token = mRequest.token;
+ mRequest = null;
for (IDeviceStateManagerCallback callback : mCallbacks) {
try {
callback.onRequestCanceled(token);
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index f04a9f7..e16a2f8 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -288,9 +288,8 @@
}
@Override
- public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
- final int deviceType) {
- }
+ public void addVendorCommandListener(
+ final IHdmiVendorCommandListener listener, final int vendorId) {}
@Override
public void sendVendorCommand(final int deviceType, final int targetAddress,
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index ddcab6e..5dcc599 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -61,5 +61,6 @@
<permission name="android.permission.READ_DREAM_SUPPRESSION"/>
<permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
+ <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index d95644a..ae350ec 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -74,5 +74,6 @@
<permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
<permission name="android.permission.FORCE_STOP_PACKAGES" />
<permission name="android.permission.ACCESS_FPS_COUNTER" />
+ <permission name="android.permission.CHANGE_CONFIGURATION" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 88920c8..92fca36 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -231,18 +231,6 @@
targetSdk="29">
<new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
</split-permission>
- <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
- targetSdk="33">
- <new-permission name="android.permission.READ_MEDIA_AUDIO" />
- </split-permission>
- <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
- targetSdk="33">
- <new-permission name="android.permission.READ_MEDIA_VIDEO" />
- </split-permission>
- <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
- targetSdk="33">
- <new-permission name="android.permission.READ_MEDIA_IMAGE" />
- </split-permission>
<split-permission name="android.permission.BLUETOOTH"
targetSdk="31">
<new-permission name="android.permission.BLUETOOTH_SCAN" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index b3dcc34..d002601 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -581,6 +581,7 @@
<permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
<permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/>
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
+ <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
</privapp-permissions>
<privapp-permissions package="com.android.bips">
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 4ae0fc4..5a3a033 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -272,13 +272,13 @@
<!-- fallback fonts -->
<family lang="und-Arab" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+ <font weight="400" style="normal">
NotoNaskhArabic-Regular.ttf
</font>
<font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
</family>
<family lang="und-Arab" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+ <font weight="400" style="normal">
NotoNaskhArabicUI-Regular.ttf
</font>
<font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
@@ -329,7 +329,7 @@
<font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif" postScriptName="NotoSerifThai">
+ <font weight="400" style="normal" fallbackFor="serif">
NotoSerifThai-Regular.ttf
</font>
<font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
@@ -923,16 +923,16 @@
<font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
</family>
<family lang="und-Laoo" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansLao">NotoSansLao-Regular.ttf
+ <font weight="400" style="normal">NotoSansLao-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif" postScriptName="NotoSerifLao">
+ <font weight="400" style="normal" fallbackFor="serif">
NotoSerifLao-Regular.ttf
</font>
<font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
</family>
<family lang="und-Laoo" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+ <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
</family>
@@ -1013,7 +1013,7 @@
</font>
</family>
<family lang="und-Cans">
- <font weight="400" style="normal" postScriptName="NotoSansCanadianAboriginal">
+ <font weight="400" style="normal">
NotoSansCanadianAboriginal-Regular.ttf
</font>
</family>
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 055e5ad..857af11 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -458,7 +458,7 @@
* No color information is stored.
* With this configuration, each pixel requires 1 byte of memory.
*/
- ALPHA_8 (1),
+ ALPHA_8(1),
/**
* Each pixel is stored on 2 bytes and only the RGB channels are
@@ -479,7 +479,7 @@
* short color = (R & 0x1f) << 11 | (G & 0x3f) << 5 | (B & 0x1f);
* </pre>
*/
- RGB_565 (3),
+ RGB_565(3),
/**
* Each pixel is stored on 2 bytes. The three RGB color channels
@@ -501,7 +501,7 @@
* it is advised to use {@link #ARGB_8888} instead.
*/
@Deprecated
- ARGB_4444 (4),
+ ARGB_4444(4),
/**
* Each pixel is stored on 4 bytes. Each channel (RGB and alpha
@@ -516,10 +516,10 @@
* int color = (A & 0xff) << 24 | (B & 0xff) << 16 | (G & 0xff) << 8 | (R & 0xff);
* </pre>
*/
- ARGB_8888 (5),
+ ARGB_8888(5),
/**
- * Each pixels is stored on 8 bytes. Each channel (RGB and alpha
+ * Each pixel is stored on 8 bytes. Each channel (RGB and alpha
* for translucency) is stored as a
* {@link android.util.Half half-precision floating point value}.
*
@@ -531,7 +531,7 @@
* long color = (A & 0xffff) << 48 | (B & 0xffff) << 32 | (G & 0xffff) << 16 | (R & 0xffff);
* </pre>
*/
- RGBA_F16 (6),
+ RGBA_F16(6),
/**
* Special configuration, when bitmap is stored only in graphic memory.
@@ -540,13 +540,29 @@
* It is optimal for cases, when the only operation with the bitmap is to draw it on a
* screen.
*/
- HARDWARE (7);
+ HARDWARE(7),
+
+ /**
+ * Each pixel is stored on 4 bytes. Each RGB channel is stored with 10 bits of precision
+ * (1024 possible values). There is an additional alpha channel that is stored with 2 bits
+ * of precision (4 possible values).
+ *
+ * This configuration is suited for wide-gamut and HDR content which does not require alpha
+ * blending, such that the memory cost is the same as ARGB_8888 while enabling higher color
+ * precision.
+ *
+ * <p>Use this formula to pack into 32 bits:</p>
+ * <pre class="prettyprint">
+ * int color = (A & 0x3) << 30 | (B & 0x3ff) << 20 | (G & 0x3ff) << 10 | (R & 0x3ff);
+ * </pre>
+ */
+ RGBA_1010102(8);
@UnsupportedAppUsage
final int nativeInt;
private static Config sConfigs[] = {
- null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE
+ null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE, RGBA_1010102
};
Config(int ni) {
@@ -1000,8 +1016,8 @@
* @param width The width of the bitmap
* @param height The height of the bitmap
* @param config The bitmap config to create.
- * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
- * mark the bitmap as opaque. Doing so will clear the bitmap in black
+ * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be
+ * used to mark the bitmap as opaque. Doing so will clear the bitmap in black
* instead of transparent.
*
* @throws IllegalArgumentException if the width or height are <= 0, or if
@@ -1019,8 +1035,8 @@
* @param width The width of the bitmap
* @param height The height of the bitmap
* @param config The bitmap config to create.
- * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
- * mark the bitmap as opaque. Doing so will clear the bitmap in black
+ * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be
+ * used to mark the bitmap as opaque. Doing so will clear the bitmap in black
* instead of transparent.
* @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16}
* and {@link ColorSpace.Named#SRGB sRGB} or
@@ -1050,8 +1066,8 @@
* @param width The width of the bitmap
* @param height The height of the bitmap
* @param config The bitmap config to create.
- * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
- * mark the bitmap as opaque. Doing so will clear the bitmap in black
+ * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be
+ * used to mark the bitmap as opaque. Doing so will clear the bitmap in black
* instead of transparent.
*
* @throws IllegalArgumentException if the width or height are <= 0, or if
@@ -1074,8 +1090,8 @@
* @param width The width of the bitmap
* @param height The height of the bitmap
* @param config The bitmap config to create.
- * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
- * mark the bitmap as opaque. Doing so will clear the bitmap in black
+ * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be
+ * used to mark the bitmap as opaque. Doing so will clear the bitmap in black
* instead of transparent.
* @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16}
* and {@link ColorSpace.Named#SRGB sRGB} or
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index cdf746f..f440b69 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -454,7 +454,8 @@
* @param challenge is a non-empty byte array whose contents should be unique, fresh and
* provided by the issuing authority. The value provided is embedded in the
* generated CBOR and enables the issuing authority to verify that the
- * returned proof is fresh.
+ * returned proof is fresh. Implementations are required to support
+ * challenges at least 32 bytes of length.
* @return the COSE_Sign1 data structure above
*/
public @NonNull byte[] proveOwnership(@NonNull byte[] challenge) {
@@ -485,7 +486,8 @@
* @param challenge is a non-empty byte array whose contents should be unique, fresh and
* provided by the issuing authority. The value provided is embedded in the
* generated CBOR and enables the issuing authority to verify that the
- * returned proof is fresh.
+ * returned proof is fresh. Implementations are required to support
+ * challenges at least 32 bytes of length.
* @return the COSE_Sign1 data structure above
*/
public @NonNull byte[] delete(@NonNull byte[] challenge) {
diff --git a/identity/java/android/security/identity/WritableIdentityCredential.java b/identity/java/android/security/identity/WritableIdentityCredential.java
index 305d0ea..6d56964 100644
--- a/identity/java/android/security/identity/WritableIdentityCredential.java
+++ b/identity/java/android/security/identity/WritableIdentityCredential.java
@@ -59,7 +59,8 @@
* @param challenge is a non-empty byte array whose contents should be unique, fresh and
* provided by the issuing authority. The value provided is embedded in the
* attestation extension and enables the issuing authority to verify that the
- * attestation certificate is fresh.
+ * attestation certificate is fresh. Implementations are required to support
+ * challenges at least 32 bytes of length.
* @return the X.509 certificate for this credential's CredentialKey.
*/
public abstract @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index e2bc360..9384e2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -245,7 +245,8 @@
}
}
- private void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {
+ private void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+ List<Rect> unrestricted) {
synchronized (mDisplays) {
if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
Slog.w(TAG, "Skipping onKeepClearAreasChanged on unknown"
@@ -253,7 +254,8 @@
return;
}
for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
- mDisplayChangedListeners.get(i).onKeepClearAreasChanged(displayId, keepClearAreas);
+ mDisplayChangedListeners.get(i)
+ .onKeepClearAreasChanged(displayId, restricted, unrestricted);
}
}
}
@@ -318,9 +320,10 @@
}
@Override
- public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {
+ public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+ List<Rect> unrestricted) {
mMainExecutor.execute(() -> {
- DisplayController.this.onKeepClearAreasChanged(displayId, keepClearAreas);
+ DisplayController.this.onKeepClearAreasChanged(displayId, restricted, unrestricted);
});
}
}
@@ -361,6 +364,7 @@
/**
* Called when keep-clear areas on a display have changed.
*/
- default void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {}
+ default void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+ List<Rect> unrestricted) {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index f61e624..9f4ff7c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -471,9 +471,10 @@
static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
DisplayController displayController, Context context,
@ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
return new Transitions(organizer, pool, displayController, context, mainExecutor,
- animExecutor);
+ mainHandler, animExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index eda09e3..5ebdceb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -150,43 +150,45 @@
if (inLandscape) {
final Rect leftHitRegion = new Rect();
- final Rect leftDrawRegion = topOrLeftBounds;
final Rect rightHitRegion = new Rect();
- final Rect rightDrawRegion = bottomOrRightBounds;
// If we have existing split regions use those bounds, otherwise split it 50/50
if (inSplitScreen) {
- // Add the divider bounds to each side since that counts for the hit region.
- leftHitRegion.set(topOrLeftBounds);
- leftHitRegion.right += dividerWidth / 2;
- rightHitRegion.set(bottomOrRightBounds);
- rightHitRegion.left -= dividerWidth / 2;
+ // The bounds of the existing split will have a divider bar, the hit region
+ // should include that space. Find the center of the divider bar:
+ float centerX = topOrLeftBounds.right + (dividerWidth / 2);
+ // Now set the hit regions using that center.
+ leftHitRegion.set(displayRegion);
+ leftHitRegion.right = (int) centerX;
+ rightHitRegion.set(displayRegion);
+ rightHitRegion.left = (int) centerX;
} else {
displayRegion.splitVertically(leftHitRegion, rightHitRegion);
}
- mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, leftDrawRegion));
- mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, rightDrawRegion));
+ mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, topOrLeftBounds));
+ mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, bottomOrRightBounds));
} else {
final Rect topHitRegion = new Rect();
- final Rect topDrawRegion = topOrLeftBounds;
final Rect bottomHitRegion = new Rect();
- final Rect bottomDrawRegion = bottomOrRightBounds;
// If we have existing split regions use those bounds, otherwise split it 50/50
if (inSplitScreen) {
- // Add the divider bounds to each side since that counts for the hit region.
- topHitRegion.set(topOrLeftBounds);
- topHitRegion.bottom += dividerWidth / 2;
- bottomHitRegion.set(bottomOrRightBounds);
- bottomHitRegion.top -= dividerWidth / 2;
+ // The bounds of the existing split will have a divider bar, the hit region
+ // should include that space. Find the center of the divider bar:
+ float centerX = topOrLeftBounds.bottom + (dividerWidth / 2);
+ // Now set the hit regions using that center.
+ topHitRegion.set(displayRegion);
+ topHitRegion.bottom = (int) centerX;
+ bottomHitRegion.set(displayRegion);
+ bottomHitRegion.top = (int) centerX;
} else {
displayRegion.splitHorizontally(topHitRegion, bottomHitRegion);
}
- mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topDrawRegion));
- mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomDrawRegion));
+ mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topOrLeftBounds));
+ mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomOrRightBounds));
}
} else {
// Split-screen not allowed, so only show the fullscreen target
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index fd3be2b..7307ba3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -24,7 +24,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -103,7 +102,7 @@
MATCH_PARENT));
((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
- updateContainerMargins();
+ updateContainerMargins(getResources().getConfiguration().orientation);
}
@Override
@@ -128,20 +127,18 @@
}
public void onConfigChanged(Configuration newConfig) {
- final int orientation = getResources().getConfiguration().orientation;
- if (orientation == Configuration.ORIENTATION_LANDSCAPE
+ if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
&& getOrientation() != HORIZONTAL) {
setOrientation(LinearLayout.HORIZONTAL);
- updateContainerMargins();
- } else if (orientation == Configuration.ORIENTATION_PORTRAIT
+ updateContainerMargins(newConfig.orientation);
+ } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
&& getOrientation() != VERTICAL) {
setOrientation(LinearLayout.VERTICAL);
- updateContainerMargins();
+ updateContainerMargins(newConfig.orientation);
}
}
- private void updateContainerMargins() {
- final int orientation = getResources().getConfiguration().orientation;
+ private void updateContainerMargins(int orientation) {
final float halfMargin = mDisplayMargin / 2f;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
mDropZoneView1.setContainerMargin(
@@ -156,10 +153,6 @@
}
}
- public boolean hasDropTarget() {
- return mCurrentTarget != null;
- }
-
public boolean hasDropped() {
return mHasDropped;
}
@@ -271,6 +264,9 @@
* Updates the visible drop target as the user drags.
*/
public void update(DragEvent event) {
+ if (mHasDropped) {
+ return;
+ }
// Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the
// visibility of the current region
DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(
@@ -286,7 +282,8 @@
animateHighlight(target);
} else {
// Switching between targets
- animateHighlight(target);
+ mDropZoneView1.animateSwitch();
+ mDropZoneView2.animateSwitch();
}
mCurrentTarget = target;
}
@@ -323,7 +320,7 @@
: DISABLE_NONE);
mDropZoneView1.setShowingMargin(visible);
mDropZoneView2.setShowingMargin(visible);
- ObjectAnimator animator = mDropZoneView1.getAnimator();
+ Animator animator = mDropZoneView1.getAnimator();
if (animCompleteCallback != null) {
if (animator != null) {
animator.addListener(new AnimatorListenerAdapter() {
@@ -343,17 +340,11 @@
if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_LEFT
|| target.type == DragAndDropPolicy.Target.TYPE_SPLIT_TOP) {
mDropZoneView1.setShowingHighlight(true);
- mDropZoneView1.setShowingSplash(false);
-
mDropZoneView2.setShowingHighlight(false);
- mDropZoneView2.setShowingSplash(true);
} else if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT
|| target.type == DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM) {
mDropZoneView1.setShowingHighlight(false);
- mDropZoneView1.setShowingSplash(true);
-
mDropZoneView2.setShowingHighlight(true);
- mDropZoneView2.setShowingSplash(false);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index 2f47af5..a3ee8ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -18,6 +18,7 @@
import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;
+import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
@@ -27,7 +28,6 @@
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.FloatProperty;
-import android.util.IntProperty;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -43,8 +43,8 @@
*/
public class DropZoneView extends FrameLayout {
- private static final int SPLASHSCREEN_ALPHA_INT = (int) (255 * 0.90f);
- private static final int HIGHLIGHT_ALPHA_INT = 255;
+ private static final float SPLASHSCREEN_ALPHA = 0.90f;
+ private static final float HIGHLIGHT_ALPHA = 1f;
private static final int MARGIN_ANIMATION_ENTER_DURATION = 400;
private static final int MARGIN_ANIMATION_EXIT_DURATION = 250;
@@ -61,54 +61,27 @@
}
};
- private static final IntProperty<ColorDrawable> SPLASHSCREEN_ALPHA =
- new IntProperty<ColorDrawable>("splashscreen") {
- @Override
- public void setValue(ColorDrawable d, int alpha) {
- d.setAlpha(alpha);
- }
-
- @Override
- public Integer get(ColorDrawable d) {
- return d.getAlpha();
- }
- };
-
- private static final IntProperty<ColorDrawable> HIGHLIGHT_ALPHA =
- new IntProperty<ColorDrawable>("highlight") {
- @Override
- public void setValue(ColorDrawable d, int alpha) {
- d.setAlpha(alpha);
- }
-
- @Override
- public Integer get(ColorDrawable d) {
- return d.getAlpha();
- }
- };
-
private final Path mPath = new Path();
private final float[] mContainerMargin = new float[4];
private float mCornerRadius;
private float mBottomInset;
private int mMarginColor; // i.e. color used for negative space like the container insets
- private int mHighlightColor;
private boolean mShowingHighlight;
private boolean mShowingSplash;
private boolean mShowingMargin;
- // TODO: might be more seamless to animate between splash/highlight color instead of 2 separate
- private ObjectAnimator mSplashAnimator;
- private ObjectAnimator mHighlightAnimator;
+ private int mSplashScreenColor;
+ private int mHighlightColor;
+
+ private ObjectAnimator mBackgroundAnimator;
private ObjectAnimator mMarginAnimator;
private float mMarginPercent;
// Renders a highlight or neutral transparent color
- private ColorDrawable mDropZoneDrawable;
+ private ColorDrawable mColorDrawable;
// Renders the translucent splashscreen with the app icon in the middle
private ImageView mSplashScreenView;
- private ColorDrawable mSplashBackgroundDrawable;
// Renders the margin / insets around the dropzone container
private MarginView mMarginView;
@@ -130,19 +103,14 @@
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mMarginColor = getResources().getColor(R.color.taskbar_background);
- mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
-
- mDropZoneDrawable = new ColorDrawable();
- mDropZoneDrawable.setColor(mHighlightColor);
- mDropZoneDrawable.setAlpha(0);
- setBackgroundDrawable(mDropZoneDrawable);
+ int c = getResources().getColor(android.R.color.system_accent1_500);
+ mHighlightColor = Color.argb(HIGHLIGHT_ALPHA, Color.red(c), Color.green(c), Color.blue(c));
+ mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, 0, 0, 0);
+ mColorDrawable = new ColorDrawable();
+ setBackgroundDrawable(mColorDrawable);
mSplashScreenView = new ImageView(context);
mSplashScreenView.setScaleType(ImageView.ScaleType.CENTER);
- mSplashBackgroundDrawable = new ColorDrawable();
- mSplashBackgroundDrawable.setColor(Color.WHITE);
- mSplashBackgroundDrawable.setAlpha(SPLASHSCREEN_ALPHA_INT);
- mSplashScreenView.setBackgroundDrawable(mSplashBackgroundDrawable);
addView(mSplashScreenView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mSplashScreenView.setAlpha(0f);
@@ -157,10 +125,6 @@
mMarginColor = getResources().getColor(R.color.taskbar_background);
mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
- final int alpha = mDropZoneDrawable.getAlpha();
- mDropZoneDrawable.setColor(mHighlightColor);
- mDropZoneDrawable.setAlpha(alpha);
-
if (mMarginPercent > 0) {
mMarginView.invalidate();
}
@@ -187,38 +151,39 @@
}
/** Sets the color and icon to use for the splashscreen when shown. */
- public void setAppInfo(int splashScreenColor, Drawable appIcon) {
- mSplashBackgroundDrawable.setColor(splashScreenColor);
+ public void setAppInfo(int color, Drawable appIcon) {
+ Color c = Color.valueOf(color);
+ mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, c.red(), c.green(), c.blue());
mSplashScreenView.setImageDrawable(appIcon);
}
/** @return an active animator for this view if one exists. */
@Nullable
- public ObjectAnimator getAnimator() {
+ public Animator getAnimator() {
if (mMarginAnimator != null && mMarginAnimator.isRunning()) {
return mMarginAnimator;
- } else if (mHighlightAnimator != null && mHighlightAnimator.isRunning()) {
- return mHighlightAnimator;
- } else if (mSplashAnimator != null && mSplashAnimator.isRunning()) {
- return mSplashAnimator;
+ } else if (mBackgroundAnimator != null && mBackgroundAnimator.isRunning()) {
+ return mBackgroundAnimator;
}
return null;
}
- /** Animates the splashscreen to show or hide. */
- public void setShowingSplash(boolean showingSplash) {
- if (mShowingSplash != showingSplash) {
- mShowingSplash = showingSplash;
- animateSplashToState();
- }
+ /** Animates between highlight and splashscreen depending on current state. */
+ public void animateSwitch() {
+ mShowingHighlight = !mShowingHighlight;
+ mShowingSplash = !mShowingHighlight;
+ final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
+ animateBackground(mColorDrawable.getColor(), newColor);
+ animateSplashScreenIcon();
}
/** Animates the highlight indicating the zone is hovered on or not. */
public void setShowingHighlight(boolean showingHighlight) {
- if (mShowingHighlight != showingHighlight) {
- mShowingHighlight = showingHighlight;
- animateHighlightToState();
- }
+ mShowingHighlight = showingHighlight;
+ mShowingSplash = !mShowingHighlight;
+ final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
+ animateBackground(Color.TRANSPARENT, newColor);
+ animateSplashScreenIcon();
}
/** Animates the margins around the drop zone to show or hide. */
@@ -228,40 +193,31 @@
animateMarginToState();
}
if (!mShowingMargin) {
- setShowingHighlight(false);
- setShowingSplash(false);
+ mShowingHighlight = false;
+ mShowingSplash = false;
+ animateBackground(mColorDrawable.getColor(), Color.TRANSPARENT);
+ animateSplashScreenIcon();
}
}
- private void animateSplashToState() {
- if (mSplashAnimator != null) {
- mSplashAnimator.cancel();
+ private void animateBackground(int startColor, int endColor) {
+ if (mBackgroundAnimator != null) {
+ mBackgroundAnimator.cancel();
}
- mSplashAnimator = ObjectAnimator.ofInt(mSplashBackgroundDrawable,
- SPLASHSCREEN_ALPHA,
- mSplashBackgroundDrawable.getAlpha(),
- mShowingSplash ? SPLASHSCREEN_ALPHA_INT : 0);
- if (!mShowingSplash) {
- mSplashAnimator.setInterpolator(FAST_OUT_SLOW_IN);
+ mBackgroundAnimator = ObjectAnimator.ofArgb(mColorDrawable,
+ "color",
+ startColor,
+ endColor);
+ if (!mShowingSplash && !mShowingHighlight) {
+ mBackgroundAnimator.setInterpolator(FAST_OUT_SLOW_IN);
}
- mSplashAnimator.start();
+ mBackgroundAnimator.start();
+ }
+
+ private void animateSplashScreenIcon() {
mSplashScreenView.animate().alpha(mShowingSplash ? 1f : 0f).start();
}
- private void animateHighlightToState() {
- if (mHighlightAnimator != null) {
- mHighlightAnimator.cancel();
- }
- mHighlightAnimator = ObjectAnimator.ofInt(mDropZoneDrawable,
- HIGHLIGHT_ALPHA,
- mDropZoneDrawable.getAlpha(),
- mShowingHighlight ? HIGHLIGHT_ALPHA_INT : 0);
- if (!mShowingHighlight) {
- mHighlightAnimator.setInterpolator(FAST_OUT_SLOW_IN);
- }
- mHighlightAnimator.start();
- }
-
private void animateMarginToState() {
if (mMarginAnimator != null) {
mMarginAnimator.cancel();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 17005ea..6b0d7f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -867,6 +867,10 @@
}
private void fadeExistingPip(boolean show) {
+ if (mLeash == null || !mLeash.isValid()) {
+ Log.w(TAG, "Invalid leash on fadeExistingPip: " + mLeash);
+ return;
+ }
final float alphaStart = show ? 0 : 1;
final float alphaEnd = show ? 1 : 0;
mPipAnimationController
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e592101..a2c2f59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -879,6 +879,7 @@
if (mMainUnfoldController != null && mSideUnfoldController != null) {
mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ updateUnfoldBounds();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 79c8a87..ddf01a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -25,6 +25,11 @@
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
+import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
@@ -57,16 +62,27 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
+import android.os.Handler;
import android.os.IBinder;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.view.Choreographer;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
@@ -92,6 +108,8 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
/** The default handler that handles anything not already handled. */
public class DefaultTransitionHandler implements Transitions.TransitionHandler {
@@ -118,6 +136,7 @@
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionAnimation mTransitionAnimation;
+ private final DevicePolicyManager mDevicePolicyManager;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
@@ -132,9 +151,24 @@
private ScreenRotationAnimation mRotationAnimation;
+ private Drawable mEnterpriseThumbnailDrawable;
+
+ private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ boolean isDrawable = intent.getBooleanExtra(
+ EXTRA_RESOURCE_TYPE_DRAWABLE, /* default= */ false);
+ if (!isDrawable) {
+ return;
+ }
+ updateEnterpriseThumbnailDrawable();
+ }
+ };
+
DefaultTransitionHandler(@NonNull DisplayController displayController,
@NonNull TransactionPool transactionPool, Context context,
- @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
+ @NonNull ShellExecutor animExecutor) {
mDisplayController = displayController;
mTransactionPool = transactionPool;
mContext = context;
@@ -143,9 +177,23 @@
mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
mCurrentUserId = UserHandle.myUserId();
+ mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ updateEnterpriseThumbnailDrawable();
+ mContext.registerReceiver(
+ mEnterpriseResourceUpdatedReceiver,
+ new IntentFilter(ACTION_DEVICE_POLICY_RESOURCE_UPDATED),
+ /* broadcastPermission = */ null,
+ mainHandler);
+
AttributeCache.init(context);
}
+ private void updateEnterpriseThumbnailDrawable() {
+ mEnterpriseThumbnailDrawable = mDevicePolicyManager.getDrawable(
+ WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION,
+ () -> mContext.getDrawable(R.drawable.ic_corp_badge));
+ }
+
@VisibleForTesting
static boolean isRotationSeamless(@NonNull TransitionInfo info,
DisplayController displayController) {
@@ -290,6 +338,9 @@
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
};
+ final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
+ new ArrayList<>();
+
@ColorInt int backgroundColorForTransition = 0;
final int wallpaperTransit = getWallpaperTransitType(info);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -361,13 +412,15 @@
}
}
- float cornerRadius = 0;
+ final float cornerRadius;
if (a.hasRoundedCorners() && isTask) {
// hasRoundedCorners is currently only enabled for tasks
final Context displayContext =
mDisplayController.getDisplayContext(change.getTaskInfo().displayId);
cornerRadius =
ScreenDecorationsUtils.getWindowCornerRadius(displayContext);
+ } else {
+ cornerRadius = 0;
}
if (a.getShowBackground()) {
@@ -383,12 +436,37 @@
}
}
+ boolean delayedEdgeExtension = false;
+ if (!isTask && a.hasExtension()) {
+ if (!Transitions.isOpeningType(change.getMode())) {
+ // Can screenshot now (before startTransaction is applied)
+ edgeExtendWindow(change, a, startTransaction, finishTransaction);
+ } else {
+ // Need to screenshot after startTransaction is applied otherwise activity
+ // may not be visible or ready yet.
+ postStartTransactionCallbacks
+ .add(t -> edgeExtendWindow(change, a, t, finishTransaction));
+ delayedEdgeExtension = true;
+ }
+ }
+
final Rect clipRect = Transitions.isClosingType(change.getMode())
? mRotator.getEndBoundsInStartRotation(change)
: change.getEndAbsBounds();
- startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
- mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */,
- cornerRadius, clipRect);
+
+ if (delayedEdgeExtension) {
+ // If the edge extension needs to happen after the startTransition has been
+ // applied, then we want to only start the animation after the edge extension
+ // postStartTransaction callback has been run
+ postStartTransactionCallbacks.add(t ->
+ startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
+ mTransactionPool, mMainExecutor, mAnimExecutor,
+ null /* position */, cornerRadius, clipRect));
+ } else {
+ startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
+ mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */,
+ cornerRadius, clipRect);
+ }
if (info.getAnimationOptions() != null) {
attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions(),
@@ -402,7 +480,20 @@
startTransaction, finishTransaction);
}
- startTransaction.apply();
+ // postStartTransactionCallbacks require that the start transaction is already
+ // applied to run otherwise they may result in flickers and UI inconsistencies.
+ boolean waitForStartTransactionApply = postStartTransactionCallbacks.size() > 0;
+ startTransaction.apply(waitForStartTransactionApply);
+
+ // Run tasks that require startTransaction to already be applied
+ for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback :
+ postStartTransactionCallbacks) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ postStartTransactionCallback.accept(t);
+ t.apply();
+ mTransactionPool.release(t);
+ }
+
mRotator.cleanUp(finishTransaction);
TransitionMetrics.getInstance().reportAnimationStart(transition);
// run finish now in-case there are no animations
@@ -410,6 +501,117 @@
return true;
}
+ private void edgeExtendWindow(TransitionInfo.Change change,
+ Animation a, SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction) {
+ final Transformation transformationAtStart = new Transformation();
+ a.getTransformationAt(0, transformationAtStart);
+ final Transformation transformationAtEnd = new Transformation();
+ a.getTransformationAt(1, transformationAtEnd);
+
+ // We want to create an extension surface that is the maximal size and the animation will
+ // take care of cropping any part that overflows.
+ final Insets maxExtensionInsets = Insets.min(
+ transformationAtStart.getInsets(), transformationAtEnd.getInsets());
+
+ final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(),
+ change.getEndAbsBounds().height());
+ final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(),
+ change.getEndAbsBounds().width());
+ if (maxExtensionInsets.left < 0) {
+ final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ -maxExtensionInsets.left, targetSurfaceHeight);
+ final int xPos = maxExtensionInsets.left;
+ final int yPos = 0;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Left Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.top < 0) {
+ final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
+ final Rect extensionRect = new Rect(0, 0,
+ targetSurfaceWidth, -maxExtensionInsets.top);
+ final int xPos = 0;
+ final int yPos = maxExtensionInsets.top;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Top Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.right < 0) {
+ final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
+ targetSurfaceWidth, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ -maxExtensionInsets.right, targetSurfaceHeight);
+ final int xPos = targetSurfaceWidth;
+ final int yPos = 0;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Right Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.bottom < 0) {
+ final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
+ targetSurfaceWidth, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ targetSurfaceWidth, -maxExtensionInsets.bottom);
+ final int xPos = maxExtensionInsets.left;
+ final int yPos = targetSurfaceHeight;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Bottom Edge Extension", startTransaction, finishTransaction);
+ }
+ }
+
+ private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, Rect edgeBounds,
+ Rect extensionRect, int xPos, int yPos, String layerName,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction) {
+ final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
+ .setName(layerName)
+ .setParent(surfaceToExtend)
+ .setHidden(true)
+ .setCallsite("DefaultTransitionHandler#startAnimation")
+ .setOpaque(true)
+ .setBufferSize(extensionRect.width(), extensionRect.height())
+ .build();
+
+ SurfaceControl.LayerCaptureArgs captureArgs =
+ new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend)
+ .setSourceCrop(edgeBounds)
+ .setFrameScale(1)
+ .setPixelFormat(PixelFormat.RGBA_8888)
+ .setChildrenOnly(true)
+ .setAllowProtected(true)
+ .build();
+ final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer =
+ SurfaceControl.captureLayers(captureArgs);
+
+ if (edgeBuffer == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Failed to capture edge of window.");
+ return null;
+ }
+
+ android.graphics.BitmapShader shader =
+ new android.graphics.BitmapShader(edgeBuffer.asBitmap(),
+ android.graphics.Shader.TileMode.CLAMP,
+ android.graphics.Shader.TileMode.CLAMP);
+ final Paint paint = new Paint();
+ paint.setShader(shader);
+
+ final Surface surface = new Surface(edgeExtensionLayer);
+ Canvas c = surface.lockHardwareCanvas();
+ c.drawRect(extensionRect, paint);
+ surface.unlockCanvasAndPost(c);
+ surface.release();
+
+ startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
+ startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
+ startTransaction.setVisibility(edgeExtensionLayer, true);
+ finishTransaction.remove(edgeExtensionLayer);
+
+ return edgeExtensionLayer;
+ }
+
private void addBackgroundToTransition(
@NonNull SurfaceControl rootLeash,
@ColorInt int color,
@@ -632,7 +834,7 @@
final boolean isClose = Transitions.isClosingType(change.getMode());
if (isOpen) {
if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) {
- attachCrossProfileThunmbnailAnimation(animations, finishCallback, change,
+ attachCrossProfileThumbnailAnimation(animations, finishCallback, change,
cornerRadius);
} else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) {
attachThumbnailAnimation(animations, finishCallback, change, options, cornerRadius);
@@ -642,13 +844,14 @@
}
}
- private void attachCrossProfileThunmbnailAnimation(@NonNull ArrayList<Animator> animations,
+ private void attachCrossProfileThumbnailAnimation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, TransitionInfo.Change change, float cornerRadius) {
- final int thumbnailDrawableRes = change.getTaskInfo().userId == mCurrentUserId
- ? R.drawable.ic_account_circle : R.drawable.ic_corp_badge;
final Rect bounds = change.getEndAbsBounds();
+ // Show the right drawable depending on the user we're transitioning to.
+ final Drawable thumbnailDrawable = change.getTaskInfo().userId == mCurrentUserId
+ ? mContext.getDrawable(R.drawable.ic_account_circle) : mEnterpriseThumbnailDrawable;
final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail(
- thumbnailDrawableRes, bounds);
+ thumbnailDrawable, bounds);
if (thumbnail == null) {
return;
}
@@ -736,9 +939,17 @@
}
t.setMatrix(leash, transformation.getMatrix(), matrix);
t.setAlpha(leash, transformation.getAlpha());
+
+ Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
+ if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) {
+ // Clip out any overflowing edge extension
+ clipRect.inset(extensionInsets);
+ t.setCrop(leash, clipRect);
+ }
+
if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) {
// We can only apply rounded corner if a crop is set
- t.setWindowCrop(leash, clipRect);
+ t.setCrop(leash, clipRect);
t.setCornerRadius(leash, cornerRadius);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 33a98b2..86b73fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -33,6 +33,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -123,7 +124,8 @@
public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
@NonNull DisplayController displayController, @NonNull Context context,
- @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
+ @NonNull ShellExecutor animExecutor) {
mOrganizer = organizer;
mContext = context;
mMainExecutor = mainExecutor;
@@ -132,7 +134,7 @@
mPlayerImpl = new TransitionPlayerImpl();
// The very last handler (0 in the list) should be the default one.
mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
- animExecutor));
+ mainHandler, animExecutor));
// Next lowest priority is remote transitions.
mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
mHandlers.add(mRemoteTransitionHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 825320b..a6caefe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -363,6 +363,45 @@
}
@Test
+ public void testOnEligibleForLetterboxEducationActivityChanged() {
+ final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN);
+ taskInfo1.displayId = DEFAULT_DISPLAY;
+ taskInfo1.topActivityEligibleForLetterboxEducation = false;
+ final TrackingTaskListener taskListener = new TrackingTaskListener();
+ mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(taskInfo1, null);
+
+ // Task listener sent to compat UI is null if top activity isn't eligible for letterbox
+ // education.
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+
+ // Task listener is non-null if top activity is eligible for letterbox education and task
+ // is visible.
+ clearInvocations(mCompatUI);
+ final RunningTaskInfo taskInfo2 =
+ createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
+ taskInfo2.displayId = taskInfo1.displayId;
+ taskInfo2.topActivityEligibleForLetterboxEducation = true;
+ taskInfo2.isVisible = true;
+ mOrganizer.onTaskInfoChanged(taskInfo2);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+
+ // Task listener is null if task is invisible.
+ clearInvocations(mCompatUI);
+ final RunningTaskInfo taskInfo3 =
+ createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
+ taskInfo3.displayId = taskInfo1.displayId;
+ taskInfo3.topActivityEligibleForLetterboxEducation = true;
+ taskInfo3.isVisible = false;
+ mOrganizer.onTaskInfoChanged(taskInfo3);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
+
+ clearInvocations(mCompatUI);
+ mOrganizer.onTaskVanished(taskInfo1);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ }
+
+ @Test
public void testOnCameraCompatActivityChanged() {
final RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
taskInfo1.displayId = DEFAULT_DISPLAY;
@@ -375,7 +414,7 @@
// compat control.
verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
- // Task linster is non-null when request a camera compat control for a visible task.
+ // Task listener is non-null when request a camera compat control for a visible task.
clearInvocations(mCompatUI);
final RunningTaskInfo taskInfo2 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index e391713..0f4a06f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -54,7 +54,9 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.view.IDisplayWindowListener;
import android.view.IWindowManager;
@@ -84,8 +86,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
import java.util.ArrayList;
@@ -106,6 +106,7 @@
private final TestShellExecutor mMainExecutor = new TestShellExecutor();
private final ShellExecutor mAnimExecutor = new TestShellExecutor();
private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler();
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
@Before
public void setUp() {
@@ -752,7 +753,7 @@
private Transitions createTestTransitions() {
return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(),
- mContext, mMainExecutor, mAnimExecutor);
+ mContext, mMainExecutor, mMainHandler, mAnimExecutor);
}
//
// private class TestDisplayController extends DisplayController {
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index db3a108..dd272cd 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -287,29 +287,29 @@
std::mutex mVkLock;
};
+static bool checkSupport(AHardwareBuffer_Format format) {
+ AHardwareBuffer_Desc desc = {
+ .width = 1,
+ .height = 1,
+ .layers = 1,
+ .format = format,
+ .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER | AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER |
+ AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
+ };
+ UniqueAHardwareBuffer buffer = allocateAHardwareBuffer(desc);
+ return buffer != nullptr;
+}
+
bool HardwareBitmapUploader::hasFP16Support() {
- static std::once_flag sOnce;
- static bool hasFP16Support = false;
-
- // Gralloc shouldn't let us create a USAGE_HW_TEXTURE if GLES is unable to consume it, so
- // we don't need to double-check the GLES version/extension.
- std::call_once(sOnce, []() {
- AHardwareBuffer_Desc desc = {
- .width = 1,
- .height = 1,
- .layers = 1,
- .format = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT,
- .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER |
- AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER |
- AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
- };
- UniqueAHardwareBuffer buffer = allocateAHardwareBuffer(desc);
- hasFP16Support = buffer != nullptr;
- });
-
+ static bool hasFP16Support = checkSupport(AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT);
return hasFP16Support;
}
+bool HardwareBitmapUploader::has1010102Support() {
+ static bool has101012Support = checkSupport(AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM);
+ return has101012Support;
+}
+
static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) {
FormatInfo formatInfo;
switch (skBitmap.info().colorType()) {
@@ -350,6 +350,19 @@
formatInfo.type = GL_UNSIGNED_BYTE;
formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
break;
+ case kRGBA_1010102_SkColorType:
+ formatInfo.isSupported = HardwareBitmapUploader::has1010102Support();
+ if (formatInfo.isSupported) {
+ formatInfo.type = GL_UNSIGNED_INT_2_10_10_10_REV;
+ formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM;
+ formatInfo.vkFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32;
+ } else {
+ formatInfo.type = GL_UNSIGNED_BYTE;
+ formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
+ formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
+ }
+ formatInfo.format = GL_RGBA;
+ break;
default:
ALOGW("unable to create hardware bitmap of colortype: %d", skBitmap.info().colorType());
formatInfo.valid = false;
diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h
index ad7a95a..34f43cd 100644
--- a/libs/hwui/HardwareBitmapUploader.h
+++ b/libs/hwui/HardwareBitmapUploader.h
@@ -29,10 +29,12 @@
#ifdef __ANDROID__
static bool hasFP16Support();
+ static bool has1010102Support();
#else
static bool hasFP16Support() {
return true;
}
+ static bool has1010102Support() { return true; }
#endif
};
diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index 3780ba0..bc6bc45 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -57,6 +57,8 @@
return ANDROID_BITMAP_FORMAT_A_8;
case kRGBA_F16_SkColorType:
return ANDROID_BITMAP_FORMAT_RGBA_F16;
+ case kRGBA_1010102_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_1010102;
default:
return ANDROID_BITMAP_FORMAT_NONE;
}
@@ -74,6 +76,8 @@
return kAlpha_8_SkColorType;
case ANDROID_BITMAP_FORMAT_RGBA_F16:
return kRGBA_F16_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ return kRGBA_1010102_SkColorType;
default:
return kUnknown_SkColorType;
}
@@ -249,6 +253,9 @@
case ANDROID_BITMAP_FORMAT_RGBA_F16:
colorType = kRGBA_F16_SkColorType;
break;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ colorType = kRGBA_1010102_SkColorType;
+ break;
default:
return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
}
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index fc542c8..dd68f82 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -159,6 +159,8 @@
break;
case kRGBA_F16_SkColorType:
break;
+ case kRGBA_1010102_SkColorType:
+ break;
default:
return false;
}
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 4cc05ef..1c20415 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -137,9 +137,16 @@
auto* brd = reinterpret_cast<skia::BitmapRegionDecoder*>(brdHandle);
SkColorType decodeColorType = brd->computeOutputColorType(colorType);
- if (decodeColorType == kRGBA_F16_SkColorType && isHardware &&
+
+ if (isHardware) {
+ if (decodeColorType == kRGBA_F16_SkColorType &&
!uirenderer::HardwareBitmapUploader::hasFP16Support()) {
- decodeColorType = kN32_SkColorType;
+ decodeColorType = kN32_SkColorType;
+ }
+ if (decodeColorType == kRGBA_1010102_SkColorType &&
+ !uirenderer::HardwareBitmapUploader::has1010102Support()) {
+ decodeColorType = kN32_SkColorType;
+ }
}
// Set up the pixel allocator
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 77f46be..33669ac 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -365,6 +365,8 @@
return kRGB_565_LegacyBitmapConfig;
case kAlpha_8_SkColorType:
return kA8_LegacyBitmapConfig;
+ case kRGBA_1010102_SkColorType:
+ return kRGBA_1010102_LegacyBitmapConfig;
case kUnknown_SkColorType:
default:
break;
@@ -374,14 +376,10 @@
SkColorType GraphicsJNI::legacyBitmapConfigToColorType(jint legacyConfig) {
const uint8_t gConfig2ColorType[] = {
- kUnknown_SkColorType,
- kAlpha_8_SkColorType,
- kUnknown_SkColorType, // Previously kIndex_8_SkColorType,
- kRGB_565_SkColorType,
- kARGB_4444_SkColorType,
- kN32_SkColorType,
- kRGBA_F16_SkColorType,
- kN32_SkColorType
+ kUnknown_SkColorType, kAlpha_8_SkColorType,
+ kUnknown_SkColorType, // Previously kIndex_8_SkColorType,
+ kRGB_565_SkColorType, kARGB_4444_SkColorType, kN32_SkColorType,
+ kRGBA_F16_SkColorType, kN32_SkColorType, kRGBA_1010102_SkColorType,
};
if (legacyConfig < 0 || legacyConfig > kLastEnum_LegacyBitmapConfig) {
@@ -399,15 +397,12 @@
jint javaConfigId = env->GetIntField(jconfig, gBitmapConfig_nativeInstanceID);
const AndroidBitmapFormat config2BitmapFormat[] = {
- ANDROID_BITMAP_FORMAT_NONE,
- ANDROID_BITMAP_FORMAT_A_8,
- ANDROID_BITMAP_FORMAT_NONE, // Previously Config.Index_8
- ANDROID_BITMAP_FORMAT_RGB_565,
- ANDROID_BITMAP_FORMAT_RGBA_4444,
- ANDROID_BITMAP_FORMAT_RGBA_8888,
- ANDROID_BITMAP_FORMAT_RGBA_F16,
- ANDROID_BITMAP_FORMAT_NONE // Congfig.HARDWARE
- };
+ ANDROID_BITMAP_FORMAT_NONE, ANDROID_BITMAP_FORMAT_A_8,
+ ANDROID_BITMAP_FORMAT_NONE, // Previously Config.Index_8
+ ANDROID_BITMAP_FORMAT_RGB_565, ANDROID_BITMAP_FORMAT_RGBA_4444,
+ ANDROID_BITMAP_FORMAT_RGBA_8888, ANDROID_BITMAP_FORMAT_RGBA_F16,
+ ANDROID_BITMAP_FORMAT_NONE, // Congfig.HARDWARE
+ ANDROID_BITMAP_FORMAT_RGBA_1010102};
return config2BitmapFormat[javaConfigId];
}
@@ -430,6 +425,9 @@
case ANDROID_BITMAP_FORMAT_RGBA_F16:
configId = kRGBA_16F_LegacyBitmapConfig;
break;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ configId = kRGBA_1010102_LegacyBitmapConfig;
+ break;
default:
break;
}
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index ba407f2..085a905 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -34,16 +34,17 @@
// This enum must keep these int values, to match the int values
// in the java Bitmap.Config enum.
enum LegacyBitmapConfig {
- kNo_LegacyBitmapConfig = 0,
- kA8_LegacyBitmapConfig = 1,
- kIndex8_LegacyBitmapConfig = 2,
- kRGB_565_LegacyBitmapConfig = 3,
- kARGB_4444_LegacyBitmapConfig = 4,
- kARGB_8888_LegacyBitmapConfig = 5,
- kRGBA_16F_LegacyBitmapConfig = 6,
- kHardware_LegacyBitmapConfig = 7,
+ kNo_LegacyBitmapConfig = 0,
+ kA8_LegacyBitmapConfig = 1,
+ kIndex8_LegacyBitmapConfig = 2,
+ kRGB_565_LegacyBitmapConfig = 3,
+ kARGB_4444_LegacyBitmapConfig = 4,
+ kARGB_8888_LegacyBitmapConfig = 5,
+ kRGBA_16F_LegacyBitmapConfig = 6,
+ kHardware_LegacyBitmapConfig = 7,
+ kRGBA_1010102_LegacyBitmapConfig = 8,
- kLastEnum_LegacyBitmapConfig = kHardware_LegacyBitmapConfig
+ kLastEnum_LegacyBitmapConfig = kRGBA_1010102_LegacyBitmapConfig
};
static void setJavaVM(JavaVM* javaVM);
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 8ad8abc..25ed935 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -16,19 +16,18 @@
#ifndef DRAWFRAMETASK_H
#define DRAWFRAMETASK_H
-#include <optional>
-#include <vector>
-
-#include <performance_hint_private.h>
+#include <android/performance_hint.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>
#include <utils/StrongPointer.h>
-#include "RenderTask.h"
+#include <optional>
+#include <vector>
#include "../FrameInfo.h"
#include "../Rect.h"
#include "../TreeInfo.h"
+#include "RenderTask.h"
namespace android {
namespace uirenderer {
diff --git a/location/java/android/location/LastLocationRequest.java b/location/java/android/location/LastLocationRequest.java
index 73c5c82..fe0a14f 100644
--- a/location/java/android/location/LastLocationRequest.java
+++ b/location/java/android/location/LastLocationRequest.java
@@ -16,6 +16,9 @@
package android.location;
+import static android.Manifest.permission.LOCATION_BYPASS;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -220,8 +223,9 @@
*
* @hide
*/
+ // TODO: remove WRITE_SECURE_SETTINGS.
@SystemApi
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
public @NonNull LastLocationRequest.Builder setAdasGnssBypass(boolean adasGnssBypass) {
mAdasGnssBypass = adasGnssBypass;
return this;
@@ -238,8 +242,9 @@
*
* @hide
*/
+ // TODO: remove WRITE_SECURE_SETTINGS.
@SystemApi
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
public @NonNull Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) {
mLocationSettingsIgnored = locationSettingsIgnored;
return this;
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index d275628..59c989b 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.LOCATION_BYPASS;
import static android.Manifest.permission.LOCATION_HARDWARE;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.location.LocationRequest.createFromDeprecatedCriteria;
@@ -678,8 +679,9 @@
*
* @hide
*/
+ // TODO: remove WRITE_SECURE_SETTINGS.
@SystemApi
- @RequiresPermission(WRITE_SECURE_SETTINGS)
+ @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
public void setAdasGnssLocationEnabled(boolean enabled) {
try {
mService.setAdasGnssLocationEnabledForUser(enabled, mContext.getUser().getIdentifier());
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 587222a..59f4f5e 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -16,6 +16,9 @@
package android.location;
+import static android.Manifest.permission.LOCATION_BYPASS;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+
import static java.lang.Math.max;
import static java.lang.Math.min;
@@ -662,9 +665,10 @@
* @hide
* @deprecated LocationRequests should be treated as immutable.
*/
+ // TODO: remove WRITE_SECURE_SETTINGS.
@SystemApi
@Deprecated
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
public @NonNull LocationRequest setLocationSettingsIgnored(boolean locationSettingsIgnored) {
mBypass = locationSettingsIgnored;
return this;
@@ -1132,8 +1136,9 @@
*
* @hide
*/
+ // TODO: remove WRITE_SECURE_SETTINGS
@SystemApi
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
public @NonNull Builder setAdasGnssBypass(boolean adasGnssBypass) {
mAdasGnssBypass = adasGnssBypass;
return this;
@@ -1150,8 +1155,9 @@
*
* @hide
*/
+ // TODO: remove WRITE_SECURE_SETTINGS
@SystemApi
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
public @NonNull Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) {
mBypass = locationSettingsIgnored;
return this;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index a2704f9..15a398d 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -181,6 +181,22 @@
public static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION";
/**
+ * @hide Broadcast intent when the volume for a particular stream type changes.
+ * Includes the stream, the new volume and previous volumes.
+ * Notes:
+ * - for internal platform use only, do not make public,
+ * - never used for "remote" volume changes
+ *
+ * @see #EXTRA_VOLUME_STREAM_TYPE
+ * @see #EXTRA_VOLUME_STREAM_VALUE
+ * @see #EXTRA_PREV_VOLUME_STREAM_VALUE
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @SuppressLint("ActionValue")
+ public static final String ACTION_VOLUME_CHANGED = "android.media.VOLUME_CHANGED_ACTION";
+
+ /**
* @hide Broadcast intent when the devices for a particular stream type changes.
* Includes the stream, the new devices and previous devices.
* Notes:
@@ -244,7 +260,8 @@
/**
* @hide The stream type for the volume changed intent.
*/
- @UnsupportedAppUsage
+ @SystemApi
+ @SuppressLint("ActionValue")
public static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE";
/**
@@ -261,7 +278,8 @@
/**
* @hide The volume associated with the stream for the volume changed intent.
*/
- @UnsupportedAppUsage
+ @SystemApi
+ @SuppressLint("ActionValue")
public static final String EXTRA_VOLUME_STREAM_VALUE =
"android.media.EXTRA_VOLUME_STREAM_VALUE";
@@ -368,7 +386,7 @@
public static final int STREAM_NOTIFICATION = AudioSystem.STREAM_NOTIFICATION;
/** @hide Used to identify the volume of audio streams for phone calls when connected
* to bluetooth */
- @UnsupportedAppUsage
+ @SystemApi
public static final int STREAM_BLUETOOTH_SCO = AudioSystem.STREAM_BLUETOOTH_SCO;
/** @hide Used to identify the volume of audio streams for enforced system sounds
* in certain countries (e.g camera in Japan) */
@@ -544,6 +562,7 @@
* Indicates the volume set/adjust call is for Bluetooth absolute volume
* @hide
*/
+ @SystemApi
public static final int FLAG_BLUETOOTH_ABS_VOLUME = 1 << 6;
/**
diff --git a/media/java/android/media/BtProfileConnectionInfo.java b/media/java/android/media/BtProfileConnectionInfo.java
index d1bb41e..88b9777 100644
--- a/media/java/android/media/BtProfileConnectionInfo.java
+++ b/media/java/android/media/BtProfileConnectionInfo.java
@@ -15,39 +15,25 @@
*/
package android.media;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.bluetooth.BluetoothProfile;
import android.os.Parcel;
import android.os.Parcelable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* Contains information about Bluetooth profile connection state changed
* {@hide}
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class BtProfileConnectionInfo implements Parcelable {
- /** @hide */
- @IntDef({
- BluetoothProfile.A2DP,
- BluetoothProfile.A2DP_SINK,
- BluetoothProfile.HEADSET, // Can only be set by BtHelper
- BluetoothProfile.HEARING_AID,
- BluetoothProfile.LE_AUDIO,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface BtProfile {}
- private final @BtProfile int mProfile;
+ private final int mProfile;
private final boolean mSupprNoisy;
private final int mVolume;
private final boolean mIsLeOutput;
- private BtProfileConnectionInfo(@BtProfile int profile, boolean suppressNoisyIntent, int volume,
+ private BtProfileConnectionInfo(int profile, boolean suppressNoisyIntent, int volume,
boolean isLeOutput) {
mProfile = profile;
mSupprNoisy = suppressNoisyIntent;
@@ -59,7 +45,7 @@
* Constructor used by BtHelper when a profile is connected
* {@hide}
*/
- public BtProfileConnectionInfo(@BtProfile int profile) {
+ public BtProfileConnectionInfo(int profile) {
this(profile, false, -1, false);
}
@@ -142,7 +128,7 @@
/**
* @return The profile connection
*/
- public @BtProfile int getProfile() {
+ public int getProfile() {
return mProfile;
}
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index 5113dc2..71dc2a7 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -18,6 +18,7 @@
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2Info;
+import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
/**
@@ -27,7 +28,8 @@
void notifySessionCreated(int requestId, in RoutingSessionInfo session);
void notifySessionUpdated(in RoutingSessionInfo session);
void notifySessionReleased(in RoutingSessionInfo session);
- void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures);
+ void notifyDiscoveryPreferenceChanged(String packageName,
+ in RouteDiscoveryPreference discoveryPreference);
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 2427fa6..ee0293d 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -34,6 +34,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Describes the properties of a route.
@@ -340,10 +341,12 @@
@ConnectionState
final int mConnectionState;
final String mClientPackageName;
+ final String mPackageName;
final int mVolumeHandling;
final int mVolumeMax;
final int mVolume;
final String mAddress;
+ final Set<String> mDeduplicationIds;
final Bundle mExtras;
final String mProviderId;
@@ -357,10 +360,12 @@
mDescription = builder.mDescription;
mConnectionState = builder.mConnectionState;
mClientPackageName = builder.mClientPackageName;
+ mPackageName = builder.mPackageName;
mVolumeHandling = builder.mVolumeHandling;
mVolumeMax = builder.mVolumeMax;
mVolume = builder.mVolume;
mAddress = builder.mAddress;
+ mDeduplicationIds = builder.mDeduplicationIds;
mExtras = builder.mExtras;
mProviderId = builder.mProviderId;
}
@@ -375,10 +380,12 @@
mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mConnectionState = in.readInt();
mClientPackageName = in.readString();
+ mPackageName = in.readString();
mVolumeHandling = in.readInt();
mVolumeMax = in.readInt();
mVolume = in.readInt();
mAddress = in.readString();
+ mDeduplicationIds = Set.of(in.readStringArray());
mExtras = in.readBundle();
mProviderId = in.readString();
}
@@ -486,6 +493,17 @@
}
/**
+ * Gets the package name of the provider that published the route.
+ * <p>
+ * It is set by the system service.
+ * @hide
+ */
+ @Nullable
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
* Gets information about how volume is handled on the route.
*
* @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
@@ -518,6 +536,18 @@
return mAddress;
}
+ /**
+ * Gets the Deduplication ID of the route if available.
+ * @see RouteDiscoveryPreference#shouldRemoveDuplicates()
+ */
+ @NonNull
+ public Set<String> getDeduplicationIds() {
+ return mDeduplicationIds;
+ }
+
+ /**
+ * Gets an optional bundle with extra data.
+ */
@Nullable
public Bundle getExtras() {
return mExtras == null ? null : new Bundle(mExtras);
@@ -549,7 +579,7 @@
* Returns if the route has at least one of the specified route features.
*
* @param features the list of route features to consider
- * @return true if the route has at least one feature in the list
+ * @return {@code true} if the route has at least one feature in the list
* @hide
*/
public boolean hasAnyFeatures(@NonNull Collection<String> features) {
@@ -563,6 +593,21 @@
}
/**
+ * Returns if the route has all the specified route features.
+ *
+ * @hide
+ */
+ public boolean hasAllFeatures(@NonNull Collection<String> features) {
+ Objects.requireNonNull(features, "features must not be null");
+ for (String feature : features) {
+ if (!getFeatures().contains(feature)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Returns true if the route info has all of the required field.
* A route is valid if and only if it is obtained from
* {@link com.android.server.media.MediaRouterService}.
@@ -596,10 +641,12 @@
&& Objects.equals(mDescription, other.mDescription)
&& (mConnectionState == other.mConnectionState)
&& Objects.equals(mClientPackageName, other.mClientPackageName)
+ && Objects.equals(mPackageName, other.mPackageName)
&& (mVolumeHandling == other.mVolumeHandling)
&& (mVolumeMax == other.mVolumeMax)
&& (mVolume == other.mVolume)
&& Objects.equals(mAddress, other.mAddress)
+ && Objects.equals(mDeduplicationIds, other.mDeduplicationIds)
&& Objects.equals(mProviderId, other.mProviderId);
}
@@ -607,8 +654,8 @@
public int hashCode() {
// Note: mExtras is not included.
return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
- mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume,
- mAddress, mProviderId);
+ mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax,
+ mVolume, mAddress, mDeduplicationIds, mProviderId);
}
@Override
@@ -626,6 +673,7 @@
.append(", volumeHandling=").append(getVolumeHandling())
.append(", volumeMax=").append(getVolumeMax())
.append(", volume=").append(getVolume())
+ .append(", deduplicationIds=").append(String.join(",", getDeduplicationIds()))
.append(", providerId=").append(getProviderId())
.append(" }");
return result.toString();
@@ -647,10 +695,12 @@
TextUtils.writeToParcel(mDescription, dest, flags);
dest.writeInt(mConnectionState);
dest.writeString(mClientPackageName);
+ dest.writeString(mPackageName);
dest.writeInt(mVolumeHandling);
dest.writeInt(mVolumeMax);
dest.writeInt(mVolume);
dest.writeString(mAddress);
+ dest.writeStringArray(mDeduplicationIds.toArray(new String[mDeduplicationIds.size()]));
dest.writeBundle(mExtras);
dest.writeString(mProviderId);
}
@@ -671,10 +721,12 @@
@ConnectionState
int mConnectionState;
String mClientPackageName;
+ String mPackageName;
int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
int mVolumeMax;
int mVolume;
String mAddress;
+ Set<String> mDeduplicationIds;
Bundle mExtras;
String mProviderId;
@@ -698,6 +750,7 @@
mId = id;
mName = name;
mFeatures = new ArrayList<>();
+ mDeduplicationIds = Set.of();
}
/**
@@ -733,10 +786,12 @@
mDescription = routeInfo.mDescription;
mConnectionState = routeInfo.mConnectionState;
mClientPackageName = routeInfo.mClientPackageName;
+ mPackageName = routeInfo.mPackageName;
mVolumeHandling = routeInfo.mVolumeHandling;
mVolumeMax = routeInfo.mVolumeMax;
mVolume = routeInfo.mVolume;
mAddress = routeInfo.mAddress;
+ mDeduplicationIds = Set.copyOf(routeInfo.mDeduplicationIds);
if (routeInfo.mExtras != null) {
mExtras = new Bundle(routeInfo.mExtras);
}
@@ -860,6 +915,16 @@
}
/**
+ * Sets the package name of the route.
+ * @hide
+ */
+ @NonNull
+ public Builder setPackageName(@NonNull String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ /**
* Sets the route's volume handling.
*/
@NonNull
@@ -897,6 +962,20 @@
}
/**
+ * Sets the deduplication ID of the route.
+ * Routes have the same ID could be removed even when
+ * they are from different providers.
+ * <p>
+ * If it's {@code null}, the route will not be removed.
+ * @see RouteDiscoveryPreference#shouldRemoveDuplicates()
+ */
+ @NonNull
+ public Builder setDeduplicationIds(@NonNull Set<String> id) {
+ mDeduplicationIds = Set.copyOf(id);
+ return this;
+ }
+
+ /**
* Sets a bundle of extras for the route.
* <p>
* Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 4b32dbf..b485eb5 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -34,15 +34,18 @@
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
@@ -302,8 +305,7 @@
mSystemController = new SystemRoutingController(
ensureClientPackageNameForSystemSession(
sManager.getSystemRoutingSession(clientPackageName)));
- mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
- sManager.getPreferredFeatures(clientPackageName), true).build();
+ mDiscoveryPreference = sManager.getDiscoveryPreference(clientPackageName);
updateAllRoutesFromManager();
// Only used by non-system MediaRouter2.
@@ -1060,11 +1062,48 @@
.build();
}
+ private List<MediaRoute2Info> getSortedRoutes(List<MediaRoute2Info> routes,
+ RouteDiscoveryPreference preference) {
+ if (!preference.shouldRemoveDuplicates()) {
+ return routes;
+ }
+ Map<String, Integer> packagePriority = new ArrayMap<>();
+ int count = preference.getDeduplicationPackageOrder().size();
+ for (int i = 0; i < count; i++) {
+ // the last package will have 1 as the priority
+ packagePriority.put(preference.getDeduplicationPackageOrder().get(i), count - i);
+ }
+ ArrayList<MediaRoute2Info> sortedRoutes = new ArrayList<>(routes);
+ // take the negative for descending order
+ sortedRoutes.sort(Comparator.comparingInt(
+ r -> -packagePriority.getOrDefault(r.getPackageName(), 0)));
+ return sortedRoutes;
+ }
+
private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
- RouteDiscoveryPreference discoveryRequest) {
- return routes.stream()
- .filter(route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures()))
- .collect(Collectors.toList());
+ RouteDiscoveryPreference discoveryPreference) {
+
+ Set<String> deduplicationIdSet = new ArraySet<>();
+
+ List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+ for (MediaRoute2Info route : getSortedRoutes(routes, discoveryPreference)) {
+ if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures())
+ || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
+ continue;
+ }
+ if (!discoveryPreference.getAllowedPackages().isEmpty()
+ && !discoveryPreference.getAllowedPackages().contains(route.getPackageName())) {
+ continue;
+ }
+ if (discoveryPreference.shouldRemoveDuplicates()) {
+ if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
+ continue;
+ }
+ deduplicationIdSet.addAll(route.getDeduplicationIds());
+ }
+ filteredRoutes.add(route);
+ }
+ return filteredRoutes;
}
private void updateAllRoutesFromManager() {
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 83fa7c2..8635c0e 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -29,6 +29,8 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -36,15 +38,18 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -84,7 +89,8 @@
@GuardedBy("mRoutesLock")
private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
@NonNull
- final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>();
+ final ConcurrentMap<String, RouteDiscoveryPreference> mDiscoveryPreferenceMap =
+ new ConcurrentHashMap<>();
private final AtomicInteger mNextRequestId = new AtomicInteger(1);
private final CopyOnWriteArrayList<TransferRequest> mTransferRequests =
@@ -247,25 +253,8 @@
*/
@NonNull
public List<MediaRoute2Info> getAvailableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
- Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
-
- List<MediaRoute2Info> routes = new ArrayList<>();
-
- String packageName = sessionInfo.getClientPackageName();
- List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
- if (preferredFeatures == null) {
- preferredFeatures = Collections.emptyList();
- }
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : mRoutes.values()) {
- if (route.hasAnyFeatures(preferredFeatures)
- || sessionInfo.getSelectedRoutes().contains(route.getId())
- || sessionInfo.getTransferableRoutes().contains(route.getId())) {
- routes.add(route);
- }
- }
- }
- return routes;
+ return getFilteredRoutes(sessionInfo, /*includeSelectedRoutes=*/true,
+ null);
}
/**
@@ -281,27 +270,70 @@
*/
@NonNull
public List<MediaRoute2Info> getTransferableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
+ return getFilteredRoutes(sessionInfo, /*includeSelectedRoutes=*/false,
+ (route) -> sessionInfo.isSystemSession() ^ route.isSystemRoute());
+ }
+
+ private List<MediaRoute2Info> getSortedRoutes(RouteDiscoveryPreference preference) {
+ if (!preference.shouldRemoveDuplicates()) {
+ synchronized (mRoutesLock) {
+ return List.copyOf(mRoutes.values());
+ }
+ }
+ Map<String, Integer> packagePriority = new ArrayMap<>();
+ int count = preference.getDeduplicationPackageOrder().size();
+ for (int i = 0; i < count; i++) {
+ // the last package will have 1 as the priority
+ packagePriority.put(preference.getDeduplicationPackageOrder().get(i), count - i);
+ }
+ ArrayList<MediaRoute2Info> routes;
+ synchronized (mRoutesLock) {
+ routes = new ArrayList<>(mRoutes.values());
+ }
+ // take the negative for descending order
+ routes.sort(Comparator.comparingInt(
+ r -> -packagePriority.getOrDefault(r.getPackageName(), 0)));
+ return routes;
+ }
+
+ private List<MediaRoute2Info> getFilteredRoutes(@NonNull RoutingSessionInfo sessionInfo,
+ boolean includeSelectedRoutes,
+ @Nullable Predicate<MediaRoute2Info> additionalFilter) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
List<MediaRoute2Info> routes = new ArrayList<>();
+ Set<String> deduplicationIdSet = new ArraySet<>();
String packageName = sessionInfo.getClientPackageName();
- List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
- if (preferredFeatures == null) {
- preferredFeatures = Collections.emptyList();
- }
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : mRoutes.values()) {
- if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
- routes.add(route);
+ RouteDiscoveryPreference discoveryPreference =
+ mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY);
+
+ for (MediaRoute2Info route : getSortedRoutes(discoveryPreference)) {
+ if (sessionInfo.getTransferableRoutes().contains(route.getId())
+ || (includeSelectedRoutes
+ && sessionInfo.getSelectedRoutes().contains(route.getId()))) {
+ routes.add(route);
+ continue;
+ }
+ if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures())
+ || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
+ continue;
+ }
+ if (!discoveryPreference.getAllowedPackages().isEmpty()
+ && !discoveryPreference.getAllowedPackages()
+ .contains(route.getPackageName())) {
+ continue;
+ }
+ if (additionalFilter != null && !additionalFilter.test(route)) {
+ continue;
+ }
+ if (discoveryPreference.shouldRemoveDuplicates()) {
+ if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
continue;
}
- // Add Phone -> Cast and Cast -> Phone
- if (route.hasAnyFeatures(preferredFeatures)
- && (sessionInfo.isSystemSession() ^ route.isSystemRoute())) {
- routes.add(route);
- }
+ deduplicationIdSet.addAll(route.getDeduplicationIds());
}
+ routes.add(route);
}
return routes;
}
@@ -310,44 +342,10 @@
* Returns the preferred features of the specified package name.
*/
@NonNull
- public List<String> getPreferredFeatures(@NonNull String packageName) {
+ public RouteDiscoveryPreference getDiscoveryPreference(@NonNull String packageName) {
Objects.requireNonNull(packageName, "packageName must not be null");
- List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
- if (preferredFeatures == null) {
- preferredFeatures = Collections.emptyList();
- }
- return preferredFeatures;
- }
-
- /**
- * Returns a list of routes which are related to the given package name in the given route list.
- */
- @NonNull
- public List<MediaRoute2Info> filterRoutesForPackage(@NonNull List<MediaRoute2Info> routes,
- @NonNull String packageName) {
- Objects.requireNonNull(routes, "routes must not be null");
- Objects.requireNonNull(packageName, "packageName must not be null");
-
- List<RoutingSessionInfo> sessions = getRoutingSessions(packageName);
- RoutingSessionInfo sessionInfo = sessions.get(sessions.size() - 1);
-
- List<MediaRoute2Info> result = new ArrayList<>();
- List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
- if (preferredFeatures == null) {
- preferredFeatures = Collections.emptyList();
- }
-
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : routes) {
- if (route.hasAnyFeatures(preferredFeatures)
- || sessionInfo.getSelectedRoutes().contains(route.getId())
- || sessionInfo.getTransferableRoutes().contains(route.getId())) {
- result.add(route);
- }
- }
- }
- return result;
+ return mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY);
}
/**
@@ -713,19 +711,19 @@
}
}
- void updatePreferredFeatures(String packageName, List<String> preferredFeatures) {
- if (preferredFeatures == null) {
- mPreferredFeaturesMap.remove(packageName);
+ void updateDiscoveryPreference(String packageName, RouteDiscoveryPreference preference) {
+ if (preference == null) {
+ mDiscoveryPreferenceMap.remove(packageName);
return;
}
- List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures);
- if ((prevFeatures == null && preferredFeatures.size() == 0)
- || Objects.equals(preferredFeatures, prevFeatures)) {
+ RouteDiscoveryPreference prevPreference =
+ mDiscoveryPreferenceMap.put(packageName, preference);
+ if (Objects.equals(preference, prevPreference)) {
return;
}
for (CallbackRecord record : mCallbackRecords) {
record.mExecutor.execute(() -> record.mCallback
- .onPreferredFeaturesChanged(packageName, preferredFeatures));
+ .onDiscoveryPreferenceChanged(packageName, preference));
}
}
@@ -1047,6 +1045,17 @@
@NonNull List<String> preferredFeatures) {}
/**
+ * Called when the preferred route features of an app is changed.
+ *
+ * @param packageName the package name of the application
+ * @param discoveryPreference the new discovery preference set by the application.
+ */
+ default void onDiscoveryPreferenceChanged(@NonNull String packageName,
+ @NonNull RouteDiscoveryPreference discoveryPreference) {
+ onPreferredFeaturesChanged(packageName, discoveryPreference.getPreferredFeatures());
+ }
+
+ /**
* Called when a previous request has failed.
*
* @param reason the reason that the request has failed. Can be one of followings:
@@ -1125,9 +1134,10 @@
}
@Override
- public void notifyPreferredFeaturesChanged(String packageName, List<String> features) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures,
- MediaRouter2Manager.this, packageName, features));
+ public void notifyDiscoveryPreferenceChanged(String packageName,
+ RouteDiscoveryPreference discoveryPreference) {
+ mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateDiscoveryPreference,
+ MediaRouter2Manager.this, packageName, discoveryPreference));
}
@Override
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index 37fee84..0045018 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -64,6 +64,13 @@
@NonNull
private final List<String> mPreferredFeatures;
+ @NonNull
+ private final List<String> mRequiredFeatures;
+ @NonNull
+ private final List<String> mPackagesOrder;
+ @NonNull
+ private final List<String> mAllowedPackages;
+
private final boolean mShouldPerformActiveScan;
@Nullable
private final Bundle mExtras;
@@ -78,12 +85,18 @@
RouteDiscoveryPreference(@NonNull Builder builder) {
mPreferredFeatures = builder.mPreferredFeatures;
+ mRequiredFeatures = builder.mRequiredFeatures;
+ mPackagesOrder = builder.mPackageOrder;
+ mAllowedPackages = builder.mAllowedPackages;
mShouldPerformActiveScan = builder.mActiveScan;
mExtras = builder.mExtras;
}
RouteDiscoveryPreference(@NonNull Parcel in) {
mPreferredFeatures = in.createStringArrayList();
+ mRequiredFeatures = in.createStringArrayList();
+ mPackagesOrder = in.createStringArrayList();
+ mAllowedPackages = in.createStringArrayList();
mShouldPerformActiveScan = in.readBoolean();
mExtras = in.readBundle();
}
@@ -96,6 +109,8 @@
* {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
* or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
* </p>
+ *
+ * @see #getRequiredFeatures()
*/
@NonNull
public List<String> getPreferredFeatures() {
@@ -103,6 +118,47 @@
}
/**
+ * Gets the required features of routes that media router would like to discover.
+ * <p>
+ * Routes that have all the required features will be discovered.
+ * They may include predefined features such as
+ * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
+ * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
+ *
+ * @see #getPreferredFeatures()
+ */
+ @NonNull
+ public List<String> getRequiredFeatures() {
+ return mRequiredFeatures;
+ }
+
+ /**
+ * Gets the ordered list of package names used to remove duplicate routes.
+ * <p>
+ * Duplicate route removal is enabled if the returned list is non-empty. Routes are deduplicated
+ * based on their {@link MediaRoute2Info#getDeduplicationIds() deduplication IDs}. If two routes
+ * have a deduplication ID in common, only the route from the provider whose package name is
+ * first in the provided list will remain.
+ *
+ * @see #shouldRemoveDuplicates()
+ */
+ @NonNull
+ public List<String> getDeduplicationPackageOrder() {
+ return mPackagesOrder;
+ }
+
+ /**
+ * Gets the list of allowed packages.
+ * <p>
+ * If it's not empty, it will only discover routes from the provider whose package name
+ * belongs to the list.
+ */
+ @NonNull
+ public List<String> getAllowedPackages() {
+ return mAllowedPackages;
+ }
+
+ /**
* Gets whether active scanning should be performed.
* <p>
* If any of discovery preferences sets this as {@code true}, active scanning will
@@ -114,6 +170,15 @@
}
/**
+ * Gets whether duplicate routes removal is enabled.
+ *
+ * @see #getDeduplicationPackageOrder()
+ */
+ public boolean shouldRemoveDuplicates() {
+ return !mPackagesOrder.isEmpty();
+ }
+
+ /**
* @hide
*/
public Bundle getExtras() {
@@ -128,6 +193,9 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStringList(mPreferredFeatures);
+ dest.writeStringList(mRequiredFeatures);
+ dest.writeStringList(mPackagesOrder);
+ dest.writeStringList(mAllowedPackages);
dest.writeBoolean(mShouldPerformActiveScan);
dest.writeBundle(mExtras);
}
@@ -155,14 +223,17 @@
return false;
}
RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
- //TODO: Make this order-free
return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
+ && Objects.equals(mRequiredFeatures, other.mRequiredFeatures)
+ && Objects.equals(mPackagesOrder, other.mPackagesOrder)
+ && Objects.equals(mAllowedPackages, other.mAllowedPackages)
&& mShouldPerformActiveScan == other.mShouldPerformActiveScan;
}
@Override
public int hashCode() {
- return Objects.hash(mPreferredFeatures, mShouldPerformActiveScan);
+ return Objects.hash(mPreferredFeatures, mRequiredFeatures, mPackagesOrder, mAllowedPackages,
+ mShouldPerformActiveScan);
}
/**
@@ -170,13 +241,21 @@
*/
public static final class Builder {
List<String> mPreferredFeatures;
+ List<String> mRequiredFeatures;
+ List<String> mPackageOrder;
+ List<String> mAllowedPackages;
+
boolean mActiveScan;
+
Bundle mExtras;
public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) {
Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null");
mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
.collect(Collectors.toList());
+ mRequiredFeatures = List.of();
+ mPackageOrder = List.of();
+ mAllowedPackages = List.of();
mActiveScan = activeScan;
}
@@ -184,12 +263,15 @@
Objects.requireNonNull(preference, "preference must not be null");
mPreferredFeatures = preference.getPreferredFeatures();
+ mRequiredFeatures = preference.getRequiredFeatures();
+ mPackageOrder = preference.getDeduplicationPackageOrder();
+ mAllowedPackages = preference.getAllowedPackages();
mActiveScan = preference.shouldPerformActiveScan();
mExtras = preference.getExtras();
}
/**
- * A constructor to combine all of the preferences into a single preference.
+ * A constructor to combine all the preferences into a single preference.
* It ignores extras of preferences.
*
* @hide
@@ -224,6 +306,30 @@
}
/**
+ * Sets the required route features to discover.
+ */
+ @NonNull
+ public Builder setRequiredFeatures(@NonNull List<String> requiredFeatures) {
+ Objects.requireNonNull(requiredFeatures, "preferredFeatures must not be null");
+ mRequiredFeatures = requiredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
+ .collect(Collectors.toList());
+ return this;
+ }
+
+ /**
+ * Sets the list of package names of providers that media router would like to discover.
+ * <p>
+ * If it's non-empty, media router only discovers route from the provider in the list.
+ * The default value is empty, which discovers routes from all providers.
+ */
+ @NonNull
+ public Builder setAllowedPackages(@NonNull List<String> allowedPackages) {
+ Objects.requireNonNull(allowedPackages, "allowedPackages must not be null");
+ mAllowedPackages = List.copyOf(allowedPackages);
+ return this;
+ }
+
+ /**
* Sets if active scanning should be performed.
* <p>
* Since active scanning uses more system resources, set this as {@code true} only
@@ -237,6 +343,24 @@
}
/**
+ * Sets the order of packages to use when removing duplicate routes.
+ * <p>
+ * Routes are deduplicated based on their
+ * {@link MediaRoute2Info#getDeduplicationIds() deduplication IDs}.
+ * If two routes have a deduplication ID in common, only the route from the provider whose
+ * package name is first in the provided list will remain.
+ *
+ * @param packageOrder ordered list of package names used to remove duplicate routes, or an
+ * empty list if deduplication should not be enabled.
+ */
+ @NonNull
+ public Builder setDeduplicationPackageOrder(@NonNull List<String> packageOrder) {
+ Objects.requireNonNull(packageOrder, "packageOrder must not be null");
+ mPackageOrder = List.copyOf(packageOrder);
+ return this;
+ }
+
+ /**
* Sets the extras of the route.
* @hide
*/
diff --git a/media/native/midi/amidi.cpp b/media/native/midi/amidi.cpp
index aa076e8..fd8a06d 100644
--- a/media/native/midi/amidi.cpp
+++ b/media/native/midi/amidi.cpp
@@ -401,10 +401,14 @@
ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort,
const uint8_t *data, size_t numBytes, int64_t timestamp) {
- if (inputPort == nullptr || data == nullptr) {
+ if (inputPort == nullptr || data == nullptr || numBytes < 0 || timestamp < 0) {
return AMEDIA_ERROR_INVALID_PARAMETER;
}
+ if (numBytes == 0) {
+ return 0;
+ }
+
// AMIDI_logBuffer(data, numBytes);
uint8_t writeBuffer[AMIDI_BUFFER_SIZE + AMIDI_PACKET_OVERHEAD];
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 35c794e..b7beb6e 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -309,24 +309,27 @@
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
AThermal_registerThermalStatusListener; # introduced=30
AThermal_unregisterThermalStatusListener; # introduced=30
AThermal_getThermalHeadroom; # introduced=31
+ APerformanceHint_getManager; # introduced=Tiramisu
+ APerformanceHint_createSession; # introduced=Tiramisu
+ APerformanceHint_getPreferredUpdateRateNanos; # introduced=Tiramisu
+ APerformanceHint_updateTargetWorkDuration; # introduced=Tiramisu
+ APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu
+ APerformanceHint_closeSession; # introduced=Tiramisu
local:
*;
};
LIBANDROID_PLATFORM {
global:
- APerformanceHint_getManager;
- APerformanceHint_createSession;
- APerformanceHint_getPreferredUpdateRateNanos;
- APerformanceHint_updateTargetWorkDuration;
- APerformanceHint_reportActualWorkDuration;
- APerformanceHint_closeSession;
APerformanceHint_setIHintManagerForTesting;
extern "C++" {
ASurfaceControl_registerSurfaceStatsListener*;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 51a0c99..0c36051 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -16,17 +16,18 @@
#define LOG_TAG "perf_hint"
-#include <utility>
-#include <vector>
-
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
+#include <android/performance_hint.h>
#include <binder/Binder.h>
#include <binder/IBinder.h>
#include <binder/IServiceManager.h>
#include <performance_hint_private.h>
#include <utils/SystemClock.h>
+#include <utility>
+#include <vector>
+
using namespace android;
using namespace android::os;
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 284e9ee..b17850e 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -18,10 +18,12 @@
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
+#include <android/performance_hint.h>
#include <binder/IBinder.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <performance_hint_private.h>
+
#include <memory>
#include <vector>
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index a0f3098..bb25274 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -198,14 +198,16 @@
return kGray_8_SkColorType;
case ANDROID_BITMAP_FORMAT_RGBA_F16:
return kRGBA_F16_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ return kRGBA_1010102_SkColorType;
default:
return kUnknown_SkColorType;
}
}
int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) {
- if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE
- || format > ANDROID_BITMAP_FORMAT_RGBA_F16) {
+ if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE ||
+ format > ANDROID_BITMAP_FORMAT_RGBA_1010102) {
return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
}
@@ -290,6 +292,8 @@
return ANDROID_BITMAP_FORMAT_A_8;
case kRGBA_F16_SkColorType:
return ANDROID_BITMAP_FORMAT_RGBA_F16;
+ case kRGBA_1010102_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_1010102;
default:
return ANDROID_BITMAP_FORMAT_NONE;
}
diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp
index 223bdcdd..54538d9 100644
--- a/packages/ConnectivityT/framework-t/Android.bp
+++ b/packages/ConnectivityT/framework-t/Android.bp
@@ -39,7 +39,6 @@
"src/android/net/TrafficStats.java",
"src/android/net/UnderlyingNetworkInfo.*",
"src/android/net/netstats/**/*.*",
- "src/com/android/server/NetworkManagementSocketTagger.java",
],
path: "src",
visibility: [
@@ -126,14 +125,14 @@
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",
@@ -176,3 +175,34 @@
"//packages/modules/Connectivity:__subpackages__",
],
}
+
+cc_library_shared {
+ name: "libframework-connectivity-tiramisu-jni",
+ min_sdk_version: "30",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ // Don't warn about S API usage even with
+ // min_sdk 30: the library is only loaded
+ // on S+ devices
+ "-Wno-unguarded-availability",
+ "-Wthread-safety",
+ ],
+ srcs: [
+ "jni/android_net_TrafficStats.cpp",
+ "jni/onload.cpp",
+ ],
+ shared_libs: [
+ "liblog",
+ ],
+ static_libs: [
+ "libnativehelper_compat_libc++",
+ ],
+ stl: "none",
+ apex_available: [
+ "com.android.tethering",
+ // TODO: remove when ConnectivityT moves to APEX.
+ "//apex_available:platform",
+ ],
+}
diff --git a/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp b/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp
new file mode 100644
index 0000000..f3c58b1
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#include <android/file_descriptor_jni.h>
+#include <android/multinetwork.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor, jint tag, jint uid) {
+ int fd = AFileDescriptor_getFd(env, fileDescriptor);
+ if (fd == -1) return -EBADF;
+ return android_tag_socket_with_uid(fd, tag, uid);
+}
+
+static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) {
+ int fd = AFileDescriptor_getFd(env, fileDescriptor);
+ if (fd == -1) return -EBADF;
+ return android_untag_socket(fd);
+}
+
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*) tagSocketFd },
+ { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*) untagSocketFd },
+};
+
+int register_android_net_TrafficStats(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "android/net/TrafficStats", gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
+
diff --git a/packages/ConnectivityT/framework-t/jni/onload.cpp b/packages/ConnectivityT/framework-t/jni/onload.cpp
new file mode 100644
index 0000000..1fb42c6
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/jni/onload.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "FrameworkConnectivityJNI"
+
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+int register_android_net_TrafficStats(JNIEnv* env);
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
+ return JNI_ERR;
+ }
+
+ if (register_android_net_TrafficStats(env) < 0) return JNI_ERR;
+
+ return JNI_VERSION_1_6;
+}
+
+}; // namespace android
+
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index 84adef5..5ce7e59 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -41,6 +41,7 @@
import android.net.NetworkTemplate;
import android.net.UnderlyingNetworkInfo;
import android.net.netstats.IUsageCallback;
+import android.net.netstats.NetworkStatsDataMigrationUtils;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.netstats.provider.NetworkStatsProvider;
import android.os.Build;
@@ -126,17 +127,12 @@
private final INetworkStatsService mService;
/**
- * Type constants for reading different types of Data Usage.
+ * @deprecated Use {@link NetworkStatsDataMigrationUtils#PREFIX_XT}
+ * instead.
* @hide
*/
- // @SystemApi(client = MODULE_LIBRARIES)
+ @Deprecated
public static final String PREFIX_DEV = "dev";
- /** @hide */
- public static final String PREFIX_XT = "xt";
- /** @hide */
- public static final String PREFIX_UID = "uid";
- /** @hide */
- public static final String PREFIX_UID_TAG = "uid_tag";
/** @hide */
public static final int FLAG_POLL_ON_OPEN = 1 << 0;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
index ece54df..f472d56 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 @@
}
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 @@
@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 @@
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 @@
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 @@
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
similarity index 93%
rename from packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.aidl
index dcce706..adf9e5a 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/EthernetNetworkManagementException.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.java
new file mode 100644
index 0000000..a35f28e
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.java
@@ -0,0 +1,71 @@
+/*
+ * 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 android.net;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/** @hide */
+public final class EthernetNetworkManagementException
+ extends RuntimeException implements Parcelable {
+
+ /* @hide */
+ public EthernetNetworkManagementException(@NonNull final String errorMessage) {
+ super(errorMessage);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getMessage());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ final EthernetNetworkManagementException that = (EthernetNetworkManagementException) obj;
+
+ return Objects.equals(getMessage(), that.getMessage());
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(getMessage());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<EthernetNetworkManagementException> CREATOR =
+ new Parcelable.Creator<EthernetNetworkManagementException>() {
+ @Override
+ public EthernetNetworkManagementException[] newArray(int size) {
+ return new EthernetNetworkManagementException[size];
+ }
+
+ @Override
+ 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
similarity index 93%
rename from packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.aidl
index da00cb9..debc348 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
similarity index 80%
rename from packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java
rename to packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java
index f42c4b7..4d229d2 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 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 @@
}
/** @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 @@
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 @@
@Override
public String toString() {
- return "InternalNetworkUpdateRequest{"
+ return "EthernetNetworkUpdateRequest{"
+ "mIpConfig=" + mIpConfig
+ ", mNetworkCapabilities=" + mNetworkCapabilities + '}';
}
@@ -65,7 +65,7 @@
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 @@
}
@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 e688bea..544d02b 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 @@
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 @@
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
similarity index 80%
rename from packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IEthernetNetworkManagementListener.aidl
index 69cde3b..93edccf 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/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java
deleted file mode 100644
index 7f4e403..0000000
--- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java
+++ /dev/null
@@ -1,59 +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 android.net;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/** @hide */
-public final class InternalNetworkManagementException
- extends RuntimeException implements Parcelable {
-
- /* @hide */
- public InternalNetworkManagementException(@NonNull final Throwable t) {
- super(t);
- }
-
- private InternalNetworkManagementException(@NonNull final Parcel source) {
- super(source.readString());
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(getCause().getMessage());
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @NonNull
- public static final Parcelable.Creator<InternalNetworkManagementException> CREATOR =
- new Parcelable.Creator<InternalNetworkManagementException>() {
- @Override
- public InternalNetworkManagementException[] newArray(int size) {
- return new InternalNetworkManagementException[size];
- }
-
- @Override
- public InternalNetworkManagementException createFromParcel(@NonNull Parcel source) {
- return new InternalNetworkManagementException(source);
- }
- };
-}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
index c2f0cdf..bc836d8 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
@@ -31,12 +31,9 @@
import android.os.Binder;
import android.os.Build;
import android.os.RemoteException;
+import android.os.StrictMode;
import android.util.Log;
-import com.android.server.NetworkManagementSocketTagger;
-
-import dalvik.system.SocketTagger;
-
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.DatagramSocket;
@@ -56,6 +53,10 @@
* use {@link NetworkStatsManager} instead.
*/
public class TrafficStats {
+ static {
+ System.loadLibrary("framework-connectivity-tiramisu-jni");
+ }
+
private static final String TAG = TrafficStats.class.getSimpleName();
/**
* The return value to indicate that the device does not support the statistic.
@@ -232,9 +233,68 @@
*/
@SystemApi(client = MODULE_LIBRARIES)
public static void attachSocketTagger() {
- NetworkManagementSocketTagger.install();
+ dalvik.system.SocketTagger.set(new SocketTagger());
}
+ private static class SocketTagger extends dalvik.system.SocketTagger {
+
+ // TODO: set to false
+ private static final boolean LOGD = true;
+
+ SocketTagger() {
+ }
+
+ @Override
+ public void tag(FileDescriptor fd) throws SocketException {
+ final UidTag tagInfo = sThreadUidTag.get();
+ if (LOGD) {
+ Log.d(TAG, "tagSocket(" + fd.getInt$() + ") with statsTag=0x"
+ + Integer.toHexString(tagInfo.tag) + ", statsUid=" + tagInfo.uid);
+ }
+ if (tagInfo.tag == -1) {
+ StrictMode.noteUntaggedSocket();
+ }
+
+ if (tagInfo.tag == -1 && tagInfo.uid == -1) return;
+ final int errno = native_tagSocketFd(fd, tagInfo.tag, tagInfo.uid);
+ if (errno < 0) {
+ Log.i(TAG, "tagSocketFd(" + fd.getInt$() + ", "
+ + tagInfo.tag + ", "
+ + tagInfo.uid + ") failed with errno" + errno);
+ }
+ }
+
+ @Override
+ public void untag(FileDescriptor fd) throws SocketException {
+ if (LOGD) {
+ Log.i(TAG, "untagSocket(" + fd.getInt$() + ")");
+ }
+
+ final UidTag tagInfo = sThreadUidTag.get();
+ if (tagInfo.tag == -1 && tagInfo.uid == -1) return;
+
+ final int errno = native_untagSocketFd(fd);
+ if (errno < 0) {
+ Log.w(TAG, "untagSocket(" + fd.getInt$() + ") failed with errno " + errno);
+ }
+ }
+ }
+
+ private static native int native_tagSocketFd(FileDescriptor fd, int tag, int uid);
+ private static native int native_untagSocketFd(FileDescriptor fd);
+
+ private static class UidTag {
+ public int tag = -1;
+ public int uid = -1;
+ }
+
+ private static ThreadLocal<UidTag> sThreadUidTag = new ThreadLocal<UidTag>() {
+ @Override
+ protected UidTag initialValue() {
+ return new UidTag();
+ }
+ };
+
/**
* Set active tag to use when accounting {@link Socket} traffic originating
* from the current thread. Only one active tag per thread is supported.
@@ -249,7 +309,7 @@
* @see #clearThreadStatsTag()
*/
public static void setThreadStatsTag(int tag) {
- NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
+ getAndSetThreadStatsTag(tag);
}
/**
@@ -267,7 +327,9 @@
* restore any existing values after a nested operation is finished
*/
public static int getAndSetThreadStatsTag(int tag) {
- return NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
+ final int old = sThreadUidTag.get().tag;
+ sThreadUidTag.get().tag = tag;
+ return old;
}
/**
@@ -327,7 +389,7 @@
* @see #setThreadStatsTag(int)
*/
public static int getThreadStatsTag() {
- return NetworkManagementSocketTagger.getThreadSocketStatsTag();
+ return sThreadUidTag.get().tag;
}
/**
@@ -337,7 +399,7 @@
* @see #setThreadStatsTag(int)
*/
public static void clearThreadStatsTag() {
- NetworkManagementSocketTagger.setThreadSocketStatsTag(-1);
+ sThreadUidTag.get().tag = -1;
}
/**
@@ -357,7 +419,7 @@
*/
@SuppressLint("RequiresPermission")
public static void setThreadStatsUid(int uid) {
- NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
+ sThreadUidTag.get().uid = uid;
}
/**
@@ -368,7 +430,7 @@
* @see #setThreadStatsUid(int)
*/
public static int getThreadStatsUid() {
- return NetworkManagementSocketTagger.getThreadSocketStatsUid();
+ return sThreadUidTag.get().uid;
}
/**
@@ -395,7 +457,7 @@
*/
@SuppressLint("RequiresPermission")
public static void clearThreadStatsUid() {
- NetworkManagementSocketTagger.setThreadSocketStatsUid(-1);
+ setThreadStatsUid(-1);
}
/**
diff --git a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
deleted file mode 100644
index 8bb12a6d..0000000
--- a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.os.StrictMode;
-import android.util.Log;
-
-import dalvik.system.SocketTagger;
-
-import java.io.FileDescriptor;
-import java.net.SocketException;
-
-/**
- * Assigns tags to sockets for traffic stats.
- * @hide
- */
-public final class NetworkManagementSocketTagger extends SocketTagger {
- private static final String TAG = "NetworkManagementSocketTagger";
- private static final boolean LOGD = false;
-
- private static ThreadLocal<SocketTags> threadSocketTags = new ThreadLocal<SocketTags>() {
- @Override
- protected SocketTags initialValue() {
- return new SocketTags();
- }
- };
-
- public static void install() {
- SocketTagger.set(new NetworkManagementSocketTagger());
- }
-
- public static int setThreadSocketStatsTag(int tag) {
- final int old = threadSocketTags.get().statsTag;
- threadSocketTags.get().statsTag = tag;
- return old;
- }
-
- public static int getThreadSocketStatsTag() {
- return threadSocketTags.get().statsTag;
- }
-
- public static int setThreadSocketStatsUid(int uid) {
- final int old = threadSocketTags.get().statsUid;
- threadSocketTags.get().statsUid = uid;
- return old;
- }
-
- public static int getThreadSocketStatsUid() {
- return threadSocketTags.get().statsUid;
- }
-
- @Override
- public void tag(FileDescriptor fd) throws SocketException {
- final SocketTags options = threadSocketTags.get();
- if (LOGD) {
- Log.d(TAG, "tagSocket(" + fd.getInt$() + ") with statsTag=0x"
- + Integer.toHexString(options.statsTag) + ", statsUid=" + options.statsUid);
- }
- if (options.statsTag == -1) {
- StrictMode.noteUntaggedSocket();
- }
- // TODO: skip tagging when options would be no-op
- tagSocketFd(fd, options.statsTag, options.statsUid);
- }
-
- private void tagSocketFd(FileDescriptor fd, int tag, int uid) {
- if (tag == -1 && uid == -1) return;
-
- final int errno = native_tagSocketFd(fd, tag, uid);
- if (errno < 0) {
- Log.i(TAG, "tagSocketFd(" + fd.getInt$() + ", "
- + tag + ", "
- + uid + ") failed with errno" + errno);
- }
- }
-
- @Override
- public void untag(FileDescriptor fd) throws SocketException {
- if (LOGD) {
- Log.i(TAG, "untagSocket(" + fd.getInt$() + ")");
- }
- unTagSocketFd(fd);
- }
-
- private void unTagSocketFd(FileDescriptor fd) {
- final SocketTags options = threadSocketTags.get();
- if (options.statsTag == -1 && options.statsUid == -1) return;
-
- final int errno = native_untagSocketFd(fd);
- if (errno < 0) {
- Log.w(TAG, "untagSocket(" + fd.getInt$() + ") failed with errno " + errno);
- }
- }
-
- public static class SocketTags {
- public int statsTag = -1;
- public int statsUid = -1;
- }
-
- /**
- * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
- * format like {@code 0x7fffffff00000000}.
- */
- public static int kernelToTag(String string) {
- int length = string.length();
- if (length > 10) {
- return Long.decode(string.substring(0, length - 8)).intValue();
- } else {
- return 0;
- }
- }
-
- private static native int native_tagSocketFd(FileDescriptor fd, int tag, int uid);
- private static native int native_untagSocketFd(FileDescriptor fd);
-}
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
index 17f3455..668d1cb 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
@@ -22,8 +22,6 @@
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
-import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -470,6 +468,19 @@
}
/**
+ * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
+ * format like {@code 0x7fffffff00000000}.
+ */
+ public static int kernelToTag(String string) {
+ int length = string.length();
+ if (length > 10) {
+ return Long.decode(string.substring(0, length - 8)).intValue();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
* Parse statistics from file into given {@link NetworkStats} object. Values
* are expected to monotonically increase since device boot.
*/
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index 748b0ae..9f3371b 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -20,9 +20,6 @@
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.app.usage.NetworkStatsManager.PREFIX_DEV;
-import static android.app.usage.NetworkStatsManager.PREFIX_UID;
-import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG;
-import static android.app.usage.NetworkStatsManager.PREFIX_XT;
import static android.content.Intent.ACTION_SHUTDOWN;
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.ACTION_USER_REMOVED;
@@ -50,6 +47,9 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.net.TrafficStats.UID_TETHERING;
import static android.net.TrafficStats.UNSUPPORTED;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
import static android.os.Trace.TRACE_TAG_NETWORK;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 1d0ae99..b65e976 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -37,7 +37,7 @@
import android.widget.Button;
import com.android.internal.app.AlertActivity;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import java.io.File;
import java.io.FileInputStream;
@@ -154,8 +154,8 @@
final PackageLite pkg = result.getResult();
params.setAppPackageName(pkg.getPackageName());
params.setInstallLocation(pkg.getInstallLocation());
- params.setSize(
- PackageHelper.calculateInstalledSize(pkg, params.abiOverride));
+ params.setSize(InstallLocationUtils.calculateInstalledSize(pkg,
+ params.abiOverride));
}
} catch (IOException e) {
Log.e(LOG_TAG,
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index fcf2282..684f4de 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -50,6 +50,7 @@
"SettingsLibSettingsTransition",
"SettingsLibActivityEmbedding",
"SettingsLibButtonPreference",
+ "setupdesign",
],
// ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml
index a347345..13f8a37 100644
--- a/packages/SettingsLib/AndroidManifest.xml
+++ b/packages/SettingsLib/AndroidManifest.xml
@@ -18,4 +18,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib">
+ <application>
+ <activity
+ android:name="com.android.settingslib.users.AvatarPickerActivity"
+ android:theme="@style/SudThemeGlifV2.DayNight"/>
+ </application>
+
</manifest>
diff --git a/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml b/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml
new file mode 100644
index 0000000..97aec74
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml
@@ -0,0 +1,30 @@
+<!--
+ ~ 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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="oval">
+ <stroke
+ android:width="2dp"
+ android:color="?android:attr/colorPrimary"/>
+ </shape>
+ </item>
+ <item
+ android:left="@dimen/avatar_picker_icon_inset"
+ android:right="@dimen/avatar_picker_icon_inset"
+ android:top="@dimen/avatar_picker_icon_inset"
+ android:bottom="@dimen/avatar_picker_icon_inset"
+ android:drawable="@drawable/ic_avatar_choose_photo"/>
+</layer-list>
diff --git a/packages/SettingsLib/res/drawable/avatar_selector.xml b/packages/SettingsLib/res/drawable/avatar_selector.xml
new file mode 100644
index 0000000..ccde597
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_selector.xml
@@ -0,0 +1,25 @@
+<?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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true">
+ <shape android:shape="oval">
+ <stroke
+ android:color="?android:attr/colorPrimary"
+ android:width="@dimen/avatar_picker_padding"/>
+ </shape>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml b/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml
new file mode 100644
index 0000000..7033aae
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml
@@ -0,0 +1,30 @@
+<!--
+ ~ 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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="oval">
+ <stroke
+ android:width="2dp"
+ android:color="?android:attr/colorPrimary"/>
+ </shape>
+ </item>
+ <item
+ android:left="@dimen/avatar_picker_icon_inset"
+ android:right="@dimen/avatar_picker_icon_inset"
+ android:top="@dimen/avatar_picker_icon_inset"
+ android:bottom="@dimen/avatar_picker_icon_inset"
+ android:drawable="@drawable/ic_avatar_take_photo"/>
+</layer-list>
diff --git a/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml b/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml
new file mode 100644
index 0000000..0cc54b6
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml
@@ -0,0 +1,25 @@
+<!--
+ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?android:attr/colorPrimary"
+ android:pathData="M5.85,17.1q1.275,-0.975 2.85,-1.538Q10.275,15 12,15q1.725,0 3.3,0.563 1.575,0.562 2.85,1.537 0.875,-1.025 1.363,-2.325Q20,13.475 20,12q0,-3.325 -2.337,-5.662Q15.325,4 12,4T6.338,6.338Q4,8.675 4,12q0,1.475 0.487,2.775 0.488,1.3 1.363,2.325zM12,13q-1.475,0 -2.488,-1.012Q8.5,10.975 8.5,9.5t1.012,-2.487Q10.525,6 12,6t2.488,1.013Q15.5,8.024 15.5,9.5t-1.012,2.488Q13.475,13 12,13zM12,22q-2.075,0 -3.9,-0.788 -1.825,-0.787 -3.175,-2.137 -1.35,-1.35 -2.137,-3.175Q2,14.075 2,12t0.788,-3.9q0.787,-1.825 2.137,-3.175 1.35,-1.35 3.175,-2.137Q9.925,2 12,2t3.9,0.788q1.825,0.787 3.175,2.137 1.35,1.35 2.137,3.175Q22,9.925 22,12t-0.788,3.9q-0.787,1.825 -2.137,3.175 -1.35,1.35 -3.175,2.137Q14.075,22 12,22zM12,20q1.325,0 2.5,-0.387 1.175,-0.388 2.15,-1.113 -0.975,-0.725 -2.15,-1.113Q13.325,17 12,17t-2.5,0.387q-1.175,0.388 -2.15,1.113 0.975,0.725 2.15,1.113Q10.675,20 12,20zM12,11q0.65,0 1.075,-0.425 0.425,-0.425 0.425,-1.075 0,-0.65 -0.425,-1.075Q12.65,8 12,8q-0.65,0 -1.075,0.425Q10.5,8.85 10.5,9.5q0,0.65 0.425,1.075Q11.35,11 12,11zM12,9.5zM12,18.5z"/>
+</vector>
+
diff --git a/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml b/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml
new file mode 100644
index 0000000..b85fdc2
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?android:attr/colorPrimary"
+ android:pathData="M9,14h10l-3.45,-4.5 -2.3,3 -1.55,-2zM8,18q-0.825,0 -1.412,-0.587Q6,16.825 6,16L6,4q0,-0.825 0.588,-1.413Q7.175,2 8,2h12q0.825,0 1.413,0.587Q22,3.175 22,4v12q0,0.825 -0.587,1.413Q20.825,18 20,18zM8,16h12L20,4L8,4v12zM4,22q-0.825,0 -1.413,-0.587Q2,20.825 2,20L2,6h2v14h14v2zM8,4v12L8,4z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml b/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml
new file mode 100644
index 0000000..5c56276
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?android:attr/colorPrimary"
+ android:pathData="M12,17.5q1.875,0 3.188,-1.313Q16.5,14.876 16.5,13q0,-1.875 -1.313,-3.188Q13.876,8.5 12,8.5q-1.875,0 -3.188,1.313Q7.5,11.124 7.5,13q0,1.875 1.313,3.188Q10.124,17.5 12,17.5zM4,21q-0.825,0 -1.413,-0.587Q2,19.825 2,19L2,7q0,-0.825 0.587,-1.412Q3.175,5 4,5h3.15L9,3h6l1.85,2L20,5q0.825,0 1.413,0.588Q22,6.175 22,7v12q0,0.825 -0.587,1.413Q20.825,21 20,21zM20,19L20,7L4,7v12zM4,19L4,7v12z"/>
+</vector>
diff --git a/packages/SettingsLib/res/layout/avatar_item.xml b/packages/SettingsLib/res/layout/avatar_item.xml
new file mode 100644
index 0000000..c52f664
--- /dev/null
+++ b/packages/SettingsLib/res/layout/avatar_item.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/avatar_image"
+ android:layout_height="@dimen/avatar_size_in_picker"
+ android:layout_width="@dimen/avatar_size_in_picker"
+ android:layout_margin="@dimen/avatar_picker_margin"
+ android:layout_gravity="center"
+ android:padding="@dimen/avatar_picker_padding"
+ android:background="@drawable/avatar_selector"/>
diff --git a/packages/SettingsLib/res/layout/avatar_picker.xml b/packages/SettingsLib/res/layout/avatar_picker.xml
new file mode 100644
index 0000000..2d40bd0
--- /dev/null
+++ b/packages/SettingsLib/res/layout/avatar_picker.xml
@@ -0,0 +1,38 @@
+<?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.google.android.setupdesign.GlifLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/glif_layout"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:icon="@drawable/ic_account_circle_outline"
+ app:sucUsePartnerResource="true"
+ app:sucHeaderText="@string/avatar_picker_title">
+
+ <LinearLayout
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:gravity="center_horizontal"
+ style="@style/SudContentFrame">
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/avatar_grid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+</com.google.android.setupdesign.GlifLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w1280dp-land/dimens.xml b/packages/SettingsLib/res/values-w1280dp-land/dimens.xml
new file mode 100644
index 0000000..4097d05
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1280dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">104dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w1440dp-land/dimens.xml b/packages/SettingsLib/res/values-w1440dp-land/dimens.xml
new file mode 100644
index 0000000..764870e
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1440dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">128dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">4dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w1600dp-land/dimens.xml b/packages/SettingsLib/res/values-w1600dp-land/dimens.xml
new file mode 100644
index 0000000..872b88a
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1600dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">148dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">6dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w480dp-port/dimens.xml b/packages/SettingsLib/res/values-w480dp-port/dimens.xml
new file mode 100644
index 0000000..cab78d6
--- /dev/null
+++ b/packages/SettingsLib/res/values-w480dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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>
+ <integer name="avatar_picker_columns">3</integer>
+ <dimen name="avatar_size_in_picker">112dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/res/values-w600dp-port/dimens.xml b/packages/SettingsLib/res/values-w600dp-port/dimens.xml
new file mode 100644
index 0000000..4097d05
--- /dev/null
+++ b/packages/SettingsLib/res/values-w600dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">104dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w720dp-port/dimens.xml b/packages/SettingsLib/res/values-w720dp-port/dimens.xml
new file mode 100644
index 0000000..764870e
--- /dev/null
+++ b/packages/SettingsLib/res/values-w720dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">128dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">4dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w840dp-port/dimens.xml b/packages/SettingsLib/res/values-w840dp-port/dimens.xml
new file mode 100644
index 0000000..872b88a
--- /dev/null
+++ b/packages/SettingsLib/res/values-w840dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">148dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">6dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w960dp-land/dimens.xml b/packages/SettingsLib/res/values-w960dp-land/dimens.xml
new file mode 100644
index 0000000..8403dba
--- /dev/null
+++ b/packages/SettingsLib/res/values-w960dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">96dp</dimen>
+ <dimen name="avatar_picker_padding">6dp</dimen>
+ <dimen name="avatar_picker_margin">2dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 2b5e9cd..29a1831 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -647,4 +647,11 @@
<item>disabled</item>
</array>
+ <!-- Images offered as options in the avatar picker. If populated, the avatar_image_descriptions
+ array must also be populated with a content description for each image. -->
+ <array name="avatar_images"/>
+
+ <!-- Content descriptions for each of the images in the avatar_images array. -->
+ <string-array name="avatar_image_descriptions"/>
+
</resources>
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index b150e01..45253bb 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/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 9ee42b6..120df76 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -102,4 +102,11 @@
<dimen name="user_photo_size_in_profile_info_dialog">112dp</dimen>
<dimen name="add_a_photo_icon_size_in_profile_info_dialog">32dp</dimen>
+ <integer name="avatar_picker_columns">3</integer>
+ <dimen name="avatar_size_in_picker">96dp</dimen>
+ <dimen name="avatar_picker_padding">6dp</dimen>
+ <dimen name="avatar_picker_margin">2dp</dimen>
+
+ <dimen name="avatar_picker_icon_inset">25dp</dimen>
+
</resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 5ada028..0fe869f 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1548,4 +1548,22 @@
<!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_no_calling">No calling.</string>
+
+ <!-- Screensaver overlay which displays the time. [CHAR LIMIT=20] -->
+ <string name="dream_complication_title_time">Time</string>
+ <!-- Screensaver overlay which displays the date. [CHAR LIMIT=20] -->
+ <string name="dream_complication_title_date">Date</string>
+ <!-- Screensaver overlay which displays the weather. [CHAR LIMIT=20] -->
+ <string name="dream_complication_title_weather">Weather</string>
+ <!-- Screensaver overlay which displays air quality. [CHAR LIMIT=20] -->
+ <string name="dream_complication_title_aqi">Air Quality</string>
+ <!-- Screensaver overlay which displays cast info. [CHAR LIMIT=20] -->
+ <string name="dream_complication_title_cast_info">Cast Info</string>
+
+ <!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] -->
+ <string name="avatar_picker_title">Choose a profile picture</string>
+
+ <!-- Content description for a default user icon. [CHAR LIMIT=NONE] -->
+ <string name="default_user_icon_description">Default user icon</string>
+
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 3f322d6..f7b2974 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -16,8 +16,11 @@
package com.android.settingslib;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY;
+
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
@@ -102,8 +105,11 @@
if (mDisabledSummary) {
final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
if (summaryView != null) {
- final CharSequence disabledText = summaryView.getContext().getText(
- R.string.disabled_by_admin_summary_text);
+ final CharSequence disabledText = mContext
+ .getSystemService(DevicePolicyManager.class)
+ .getString(CONTROLLED_BY_ADMIN_SUMMARY,
+ () -> summaryView.getContext().getString(
+ R.string.disabled_by_admin_summary_text));
if (mDisabledByAdmin) {
summaryView.setText(disabledText);
} else if (mDisabledByAppOps) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index d73e45e..883e080 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -1,7 +1,10 @@
package com.android.settingslib;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
+
import android.annotation.ColorInt;
import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -124,7 +127,8 @@
String name = info != null ? info.name : null;
if (info.isManagedProfile()) {
// We use predefined values for managed profiles
- return context.getString(R.string.managed_user_title);
+ return context.getSystemService(DevicePolicyManager.class).getString(
+ WORK_PROFILE_USER_LABEL, () -> context.getString(R.string.managed_user_title));
} else if (info.isGuest()) {
name = context.getString(R.string.user_guest);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 99e3160..15ca8cd 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 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 b11bbde..7e5c124 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 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 dc109ca..6f2d4de 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 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 209507a..db6d41e 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.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 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 46e31ce..a000c09 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -34,6 +34,7 @@
import android.provider.Settings;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
@@ -50,6 +51,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -149,13 +151,13 @@
.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)
@@ -292,6 +294,11 @@
}
}
+ /** Returns whether a particular complication is enabled */
+ public boolean isComplicationEnabled(@ComplicationType int complication) {
+ return getEnabledComplications().contains(complication);
+ }
+
/** Gets all complications which have been enabled by the user. */
public Set<Integer> getEnabledComplications() {
final String enabledComplications = Settings.Secure.getString(
@@ -331,6 +338,35 @@
convertToString(enabledComplications));
}
+ /**
+ * Gets the title of a particular complication type to be displayed to the user. If there
+ * is no title, null is returned.
+ */
+ @Nullable
+ public CharSequence getComplicationTitle(@ComplicationType int complicationType) {
+ int res = 0;
+ switch (complicationType) {
+ case COMPLICATION_TYPE_TIME:
+ res = R.string.dream_complication_title_time;
+ break;
+ case COMPLICATION_TYPE_DATE:
+ res = R.string.dream_complication_title_date;
+ break;
+ case COMPLICATION_TYPE_WEATHER:
+ res = R.string.dream_complication_title_weather;
+ break;
+ case COMPLICATION_TYPE_AIR_QUALITY:
+ res = R.string.dream_complication_title_aqi;
+ break;
+ case COMPLICATION_TYPE_CAST_INFO:
+ res = R.string.dream_complication_title_cast_info;
+ break;
+ default:
+ return null;
+ }
+ return mContext.getString(res);
+ }
+
private static String convertToString(Set<Integer> set) {
return set.stream()
.map(String::valueOf)
@@ -338,6 +374,9 @@
}
private static Set<Integer> parseFromString(String string) {
+ if (TextUtils.isEmpty(string)) {
+ return new HashSet<>();
+ }
return Arrays.stream(string.split(","))
.map(Integer::parseInt)
.collect(Collectors.toSet());
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
new file mode 100644
index 0000000..61b8911
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
@@ -0,0 +1,297 @@
+/*
+ * 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.settingslib.users;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.StrictMode;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+import android.util.EventLog;
+import android.util.Log;
+
+import androidx.core.content.FileProvider;
+
+import libcore.io.Streams;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+class AvatarPhotoController {
+ private static final String TAG = "AvatarPhotoController";
+
+ private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
+ private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
+ private static final int REQUEST_CODE_CROP_PHOTO = 1003;
+ // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
+ // so we need a default photo size
+ private static final int DEFAULT_PHOTO_SIZE = 500;
+
+ private static final String IMAGES_DIR = "multi_user";
+ private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
+ private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
+
+ private final int mPhotoSize;
+
+ private final AvatarPickerActivity mActivity;
+ private final String mFileAuthority;
+
+ private final File mImagesDir;
+ private final Uri mCropPictureUri;
+ private final Uri mTakePictureUri;
+
+ AvatarPhotoController(AvatarPickerActivity activity, boolean waiting, String fileAuthority) {
+ mActivity = activity;
+ mFileAuthority = fileAuthority;
+
+ mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
+ mImagesDir.mkdir();
+ mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
+ mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
+ mPhotoSize = getPhotoSize(activity);
+ }
+
+ /**
+ * Handles activity result from containing activity/fragment after a take/choose/crop photo
+ * action result is received.
+ */
+ public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode != Activity.RESULT_OK) {
+ return false;
+ }
+ final Uri pictureUri = data != null && data.getData() != null
+ ? data.getData() : mTakePictureUri;
+
+ // Check if the result is a content uri
+ if (!ContentResolver.SCHEME_CONTENT.equals(pictureUri.getScheme())) {
+ Log.e(TAG, "Invalid pictureUri scheme: " + pictureUri.getScheme());
+ EventLog.writeEvent(0x534e4554, "172939189", -1, pictureUri.getPath());
+ return false;
+ }
+
+ switch (requestCode) {
+ case REQUEST_CODE_CROP_PHOTO:
+ mActivity.returnUriResult(pictureUri);
+ return true;
+ case REQUEST_CODE_TAKE_PHOTO:
+ case REQUEST_CODE_CHOOSE_PHOTO:
+ if (mTakePictureUri.equals(pictureUri)) {
+ if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
+ cropPhoto();
+ } else {
+ onPhotoNotCropped(pictureUri);
+ }
+ } else {
+ copyAndCropPhoto(pictureUri);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ void takePhoto() {
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
+ appendOutputExtra(intent, mTakePictureUri);
+ mActivity.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
+ }
+
+ void choosePhoto() {
+ Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES, null);
+ intent.setType("image/*");
+ mActivity.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
+ }
+
+ private void copyAndCropPhoto(final Uri pictureUri) {
+ // TODO: Replace AsyncTask
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ final ContentResolver cr = mActivity.getContentResolver();
+ try (InputStream in = cr.openInputStream(pictureUri);
+ OutputStream out = cr.openOutputStream(mTakePictureUri)) {
+ Streams.copy(in, out);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to copy photo", e);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
+ cropPhoto();
+ }
+ }
+ }.execute();
+ }
+
+ private void cropPhoto() {
+ // TODO: Use a public intent, when there is one.
+ Intent intent = new Intent("com.android.camera.action.CROP");
+ intent.setDataAndType(mTakePictureUri, "image/*");
+ appendOutputExtra(intent, mCropPictureUri);
+ appendCropExtras(intent);
+ if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
+ try {
+ StrictMode.disableDeathOnFileUriExposure();
+ mActivity.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
+ } finally {
+ StrictMode.enableDeathOnFileUriExposure();
+ }
+ } else {
+ onPhotoNotCropped(mTakePictureUri);
+ }
+ }
+
+ private void appendOutputExtra(Intent intent, Uri pictureUri) {
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
+ }
+
+ private void appendCropExtras(Intent intent) {
+ intent.putExtra("crop", "true");
+ intent.putExtra("scale", true);
+ intent.putExtra("scaleUpIfNeeded", true);
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ intent.putExtra("outputX", mPhotoSize);
+ intent.putExtra("outputY", mPhotoSize);
+ }
+
+ private void onPhotoNotCropped(final Uri data) {
+ // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
+ new AsyncTask<Void, Void, Bitmap>() {
+ @Override
+ protected Bitmap doInBackground(Void... params) {
+ // Scale and crop to a square aspect ratio
+ Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(croppedImage);
+ Bitmap fullImage;
+ try {
+ InputStream imageStream = mActivity.getContentResolver()
+ .openInputStream(data);
+ fullImage = BitmapFactory.decodeStream(imageStream);
+ } catch (FileNotFoundException fe) {
+ return null;
+ }
+ if (fullImage != null) {
+ int rotation = getRotation(mActivity, data);
+ final int squareSize = Math.min(fullImage.getWidth(),
+ fullImage.getHeight());
+ final int left = (fullImage.getWidth() - squareSize) / 2;
+ final int top = (fullImage.getHeight() - squareSize) / 2;
+
+ Matrix matrix = new Matrix();
+ RectF rectSource = new RectF(left, top,
+ left + squareSize, top + squareSize);
+ RectF rectDest = new RectF(0, 0, mPhotoSize, mPhotoSize);
+ matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
+ matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
+ canvas.drawBitmap(fullImage, matrix, new Paint());
+ return croppedImage;
+ } else {
+ // Bah! Got nothin.
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ saveBitmapToFile(bitmap, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
+ mActivity.returnUriResult(mCropPictureUri);
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+
+ /**
+ * Reads the image's exif data and determines the rotation degree needed to display the image
+ * in portrait mode.
+ */
+ private int getRotation(Context context, Uri selectedImage) {
+ int rotation = -1;
+ try {
+ InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
+ ExifInterface exif = new ExifInterface(imageStream);
+ rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
+ } catch (IOException exception) {
+ Log.e(TAG, "Error while getting rotation", exception);
+ }
+
+ switch (rotation) {
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ return 90;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ return 180;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ private void saveBitmapToFile(Bitmap bitmap, File file) {
+ try {
+ OutputStream os = new FileOutputStream(file);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
+ os.flush();
+ os.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot create temp file", e);
+ }
+ }
+
+ private static int getPhotoSize(Context context) {
+ try (Cursor cursor = context.getContentResolver().query(
+ ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
+ new String[]{ContactsContract.DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
+ if (cursor != null) {
+ cursor.moveToFirst();
+ return cursor.getInt(0);
+ } else {
+ return DEFAULT_PHOTO_SIZE;
+ }
+ }
+ }
+
+ private Uri createTempImageUri(Context context, String fileName, boolean purge) {
+ final File fullPath = new File(mImagesDir, fileName);
+ if (purge) {
+ fullPath.delete();
+ }
+ return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
new file mode 100644
index 0000000..93be66a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -0,0 +1,359 @@
+/*
+ * 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.settingslib.users;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.R;
+
+import com.google.android.setupcompat.template.FooterBarMixin;
+import com.google.android.setupcompat.template.FooterButton;
+import com.google.android.setupdesign.GlifLayout;
+import com.google.android.setupdesign.util.ThemeHelper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Activity to allow the user to choose a user profile picture.
+ *
+ * <p>Options are provided to take a photo or choose a photo using the photo picker. In addition,
+ * preselected avatar images may be provided in the resource array {@code avatar_images}. If
+ * provided, every element of that array must be a bitmap drawable.
+ *
+ * <p>If preselected images are not provided, the default avatar will be shown instead, in a range
+ * of colors.
+ *
+ * <p>This activity should be started with startActivityForResult. If a photo or a preselected image
+ * is selected, a Uri will be returned in the data field of the result intent. If a colored default
+ * avatar is selected, the chosen color will be returned as {@code EXTRA_DEFAULT_ICON_TINT_COLOR}
+ * and the data field will be empty.
+ */
+public class AvatarPickerActivity extends Activity {
+
+ static final String EXTRA_FILE_AUTHORITY = "file_authority";
+ static final String EXTRA_DEFAULT_ICON_TINT_COLOR = "default_icon_tint_color";
+
+ private static final String KEY_AWAITING_RESULT = "awaiting_result";
+ private static final String KEY_SELECTED_POSITION = "selected_position";
+
+ private boolean mWaitingForActivityResult;
+
+ private FooterButton mDoneButton;
+ private AvatarAdapter mAdapter;
+
+ private AvatarPhotoController mAvatarPhotoController;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ThemeHelper.trySetDynamicColor(this);
+ setContentView(R.layout.avatar_picker);
+ setUpButtons();
+
+ RecyclerView recyclerView = findViewById(R.id.avatar_grid);
+ mAdapter = new AvatarAdapter();
+ recyclerView.setAdapter(mAdapter);
+ recyclerView.setLayoutManager(new GridLayoutManager(this,
+ getResources().getInteger(R.integer.avatar_picker_columns)));
+
+ restoreState(savedInstanceState);
+
+ mAvatarPhotoController = new AvatarPhotoController(
+ this, mWaitingForActivityResult, getFileAuthority());
+ }
+
+ private void setUpButtons() {
+ GlifLayout glifLayout = findViewById(R.id.glif_layout);
+ FooterBarMixin mixin = glifLayout.getMixin(FooterBarMixin.class);
+
+ FooterButton secondaryButton =
+ new FooterButton.Builder(this)
+ .setText("Cancel")
+ .setListener(view -> cancel())
+ .build();
+
+ mDoneButton =
+ new FooterButton.Builder(this)
+ .setText("Done")
+ .setListener(view -> mAdapter.returnSelectionResult())
+ .build();
+ mDoneButton.setEnabled(false);
+
+ mixin.setSecondaryButton(secondaryButton);
+ mixin.setPrimaryButton(mDoneButton);
+ }
+
+ private String getFileAuthority() {
+ String authority = getIntent().getStringExtra(EXTRA_FILE_AUTHORITY);
+ if (authority == null) {
+ throw new IllegalStateException("File authority must be provided");
+ }
+ return authority;
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mWaitingForActivityResult = false;
+ mAvatarPhotoController.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult);
+ outState.putInt(KEY_SELECTED_POSITION, mAdapter.mSelectedPosition);
+ super.onSaveInstanceState(outState);
+ }
+
+ private void restoreState(Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false);
+ mAdapter.mSelectedPosition =
+ savedInstanceState.getInt(KEY_SELECTED_POSITION, AvatarAdapter.NONE);
+ }
+ }
+
+ @Override
+ public void startActivityForResult(Intent intent, int requestCode) {
+ mWaitingForActivityResult = true;
+ super.startActivityForResult(intent, requestCode);
+ }
+
+ void returnUriResult(Uri uri) {
+ Intent resultData = new Intent();
+ resultData.setData(uri);
+ setResult(RESULT_OK, resultData);
+ finish();
+ }
+
+ void returnColorResult(int color) {
+ Intent resultData = new Intent();
+ resultData.putExtra(EXTRA_DEFAULT_ICON_TINT_COLOR, color);
+ setResult(RESULT_OK, resultData);
+ finish();
+ }
+
+ private void cancel() {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
+ private class AvatarAdapter extends RecyclerView.Adapter<AvatarViewHolder> {
+
+ private static final int NONE = -1;
+
+ private final int mTakePhotoPosition;
+ private final int mChoosePhotoPosition;
+ private final int mPreselectedImageStartPosition;
+
+ private final List<Drawable> mImageDrawables;
+ private final List<String> mImageDescriptions;
+ private final TypedArray mPreselectedImages;
+ private final int[] mUserIconColors;
+ private int mSelectedPosition = NONE;
+
+ AvatarAdapter() {
+ final boolean canTakePhoto =
+ PhotoCapabilityUtils.canTakePhoto(AvatarPickerActivity.this);
+ final boolean canChoosePhoto =
+ PhotoCapabilityUtils.canChoosePhoto(AvatarPickerActivity.this);
+ mTakePhotoPosition = (canTakePhoto ? 0 : NONE);
+ mChoosePhotoPosition = (canChoosePhoto ? (canTakePhoto ? 1 : 0) : NONE);
+ mPreselectedImageStartPosition = (canTakePhoto ? 1 : 0) + (canChoosePhoto ? 1 : 0);
+
+ mPreselectedImages = getResources().obtainTypedArray(R.array.avatar_images);
+ mUserIconColors = UserIcons.getUserIconColors(getResources());
+ mImageDrawables = buildDrawableList();
+ mImageDescriptions = buildDescriptionsList();
+ }
+
+ @NonNull
+ @Override
+ public AvatarViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
+ LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+ View itemView = layoutInflater.inflate(R.layout.avatar_item, parent, false);
+ return new AvatarViewHolder(itemView);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull AvatarViewHolder viewHolder, int position) {
+ if (position == mTakePhotoPosition) {
+ viewHolder.setDrawable(getDrawable(R.drawable.avatar_take_photo_circled));
+ viewHolder.setContentDescription(getString(R.string.user_image_take_photo));
+ viewHolder.setClickListener(view -> mAvatarPhotoController.takePhoto());
+
+ } else if (position == mChoosePhotoPosition) {
+ viewHolder.setDrawable(getDrawable(R.drawable.avatar_choose_photo_circled));
+ viewHolder.setContentDescription(getString(R.string.user_image_choose_photo));
+ viewHolder.setClickListener(view -> mAvatarPhotoController.choosePhoto());
+
+ } else if (position >= mPreselectedImageStartPosition) {
+ int index = indexFromPosition(position);
+ viewHolder.setSelected(position == mSelectedPosition);
+ viewHolder.setDrawable(mImageDrawables.get(index));
+ if (mImageDescriptions != null) {
+ viewHolder.setContentDescription(mImageDescriptions.get(index));
+ } else {
+ viewHolder.setContentDescription(
+ getString(R.string.default_user_icon_description));
+ }
+ viewHolder.setClickListener(view -> {
+ if (mSelectedPosition == position) {
+ deselect(position);
+ } else {
+ select(position);
+ }
+ });
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mPreselectedImageStartPosition + mImageDrawables.size();
+ }
+
+ private List<Drawable> buildDrawableList() {
+ List<Drawable> result = new ArrayList<>();
+
+ for (int i = 0; i < mPreselectedImages.length(); i++) {
+ Drawable drawable = mPreselectedImages.getDrawable(i);
+ if (drawable instanceof BitmapDrawable) {
+ result.add(circularDrawableFrom((BitmapDrawable) drawable));
+ } else {
+ throw new IllegalStateException("Avatar drawables must be bitmaps");
+ }
+ }
+ if (!result.isEmpty()) {
+ return result;
+ }
+
+ // No preselected images. Use tinted default icon.
+ for (int i = 0; i < mUserIconColors.length; i++) {
+ result.add(UserIcons.getDefaultUserIconInColor(getResources(), mUserIconColors[i]));
+ }
+ return result;
+ }
+
+ private List<String> buildDescriptionsList() {
+ if (mPreselectedImages.length() > 0) {
+ return Arrays.asList(
+ getResources().getStringArray(R.array.avatar_image_descriptions));
+ }
+
+ return null;
+ }
+
+ private Drawable circularDrawableFrom(BitmapDrawable drawable) {
+ Bitmap bitmap = drawable.getBitmap();
+
+ RoundedBitmapDrawable roundedBitmapDrawable =
+ RoundedBitmapDrawableFactory.create(getResources(), bitmap);
+ roundedBitmapDrawable.setCircular(true);
+
+ return roundedBitmapDrawable;
+ }
+
+ private int indexFromPosition(int position) {
+ return position - mPreselectedImageStartPosition;
+ }
+
+ private void select(int position) {
+ final int oldSelection = mSelectedPosition;
+ mSelectedPosition = position;
+ notifyItemChanged(position);
+ if (oldSelection != NONE) {
+ notifyItemChanged(oldSelection);
+ } else {
+ mDoneButton.setEnabled(true);
+ }
+ }
+
+ private void deselect(int position) {
+ mSelectedPosition = NONE;
+ notifyItemChanged(position);
+ mDoneButton.setEnabled(false);
+ }
+
+ private void returnSelectionResult() {
+ int index = indexFromPosition(mSelectedPosition);
+ if (mPreselectedImages.length() > 0) {
+ int resourceId = mPreselectedImages.getResourceId(index, -1);
+ if (resourceId == -1) {
+ throw new IllegalStateException("Preselected avatar images must be resources.");
+ }
+ returnUriResult(uriForResourceId(resourceId));
+ } else {
+ returnColorResult(
+ mUserIconColors[index]);
+ }
+ }
+
+ private Uri uriForResourceId(int resourceId) {
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(getResources().getResourcePackageName(resourceId))
+ .appendPath(getResources().getResourceTypeName(resourceId))
+ .appendPath(getResources().getResourceEntryName(resourceId))
+ .build();
+ }
+ }
+
+ private static class AvatarViewHolder extends RecyclerView.ViewHolder {
+ private final ImageView mImageView;
+
+ AvatarViewHolder(View view) {
+ super(view);
+ mImageView = view.findViewById(R.id.avatar_image);
+ }
+
+ public void setDrawable(Drawable drawable) {
+ mImageView.setImageDrawable(drawable);
+ }
+
+ public void setContentDescription(String desc) {
+ mImageView.setContentDescription(desc);
+ }
+
+ public void setClickListener(View.OnClickListener listener) {
+ mImageView.setOnClickListener(listener);
+ }
+
+ public void setSelected(boolean selected) {
+ mImageView.setSelected(selected);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index 6204336..80ee86f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -25,6 +25,7 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
+import android.os.UserManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
@@ -36,6 +37,8 @@
import com.android.internal.util.UserIcons;
import com.android.settingslib.R;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.drawable.CircleFramedDrawable;
import java.io.File;
@@ -139,12 +142,20 @@
Drawable userIcon = getUserIcon(activity, defaultUserIcon);
userPhotoView.setImageDrawable(userIcon);
- if (canChangePhoto(activity)) {
- mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter,
- userPhotoView);
- } else {
- // some users can't change their photos, so we need to remove the suggestive icon
+ if (isChangePhotoRestrictedByBase(activity)) {
+ // some users can't change their photos so we need to remove the suggestive icon
content.findViewById(R.id.add_a_photo_icon).setVisibility(View.GONE);
+ } else {
+ RestrictedLockUtils.EnforcedAdmin adminRestriction =
+ getChangePhotoAdminRestriction(activity);
+ if (adminRestriction != null) {
+ userPhotoView.setOnClickListener(view ->
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
+ activity, adminRestriction));
+ } else {
+ mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter,
+ userPhotoView);
+ }
}
mEditUserInfoDialog = buildDialog(activity, content, userNameView, oldUserIcon,
@@ -203,16 +214,21 @@
}
@VisibleForTesting
- boolean canChangePhoto(Context context) {
- return (PhotoCapabilityUtils.canCropPhoto(context)
- && PhotoCapabilityUtils.canChoosePhoto(context))
- || PhotoCapabilityUtils.canTakePhoto(context);
+ boolean isChangePhotoRestrictedByBase(Context context) {
+ return RestrictedLockUtilsInternal.hasBaseUserRestriction(
+ context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
+ }
+
+ @VisibleForTesting
+ RestrictedLockUtils.EnforcedAdmin getChangePhotoAdminRestriction(Context context) {
+ return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+ context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
}
@VisibleForTesting
EditUserPhotoController createEditUserPhotoController(Activity activity,
ActivityStarter activityStarter, ImageView userPhotoView) {
return new EditUserPhotoController(activity, activityStarter, userPhotoView,
- mSavedPhoto, mWaitingForActivityResult, mFileAuthority);
+ mSavedPhoto, mFileAuthority);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index f9584a3..f8bb38b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -16,46 +16,21 @@
package com.android.settingslib.users;
+import android.annotation.NonNull;
import android.app.Activity;
-import android.content.ClipData;
-import android.content.ContentResolver;
-import android.content.Context;
import android.content.Intent;
-import android.database.Cursor;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.RectF;
import android.graphics.drawable.Drawable;
-import android.media.ExifInterface;
import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.StrictMode;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.ContactsContract.DisplayPhoto;
-import android.provider.MediaStore;
-import android.util.EventLog;
import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
import android.widget.ImageView;
-import android.widget.ListPopupWindow;
-import android.widget.TextView;
-import androidx.core.content.FileProvider;
-
+import com.android.internal.util.UserIcons;
import com.android.settingslib.R;
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.drawable.CircleFramedDrawable;
-
-import libcore.io.Streams;
+import com.android.settingslib.utils.ThreadUtils;
import java.io.File;
import java.io.FileNotFoundException;
@@ -63,8 +38,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.concurrent.ExecutionException;
/**
* This class contains logic for starting activities to take/choose/crop photo, reads and transforms
@@ -75,45 +49,30 @@
// It seems that this class generates custom request codes and they may
// collide with ours, these values are very unlikely to have a conflict.
- private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
- private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
- private static final int REQUEST_CODE_CROP_PHOTO = 1003;
- // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
- // so we need a default photo size
- private static final int DEFAULT_PHOTO_SIZE = 500;
+ private static final int REQUEST_CODE_PICK_AVATAR = 1004;
private static final String IMAGES_DIR = "multi_user";
- private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
- private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
private static final String NEW_USER_PHOTO_FILE_NAME = "NewUserPhoto.png";
- private final int mPhotoSize;
-
private final Activity mActivity;
private final ActivityStarter mActivityStarter;
private final ImageView mImageView;
private final String mFileAuthority;
private final File mImagesDir;
- private final Uri mCropPictureUri;
- private final Uri mTakePictureUri;
-
private Bitmap mNewUserPhotoBitmap;
private Drawable mNewUserPhotoDrawable;
public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
- ImageView view, Bitmap bitmap, boolean waiting, String fileAuthority) {
+ ImageView view, Bitmap bitmap, String fileAuthority) {
mActivity = activity;
mActivityStarter = activityStarter;
- mImageView = view;
mFileAuthority = fileAuthority;
mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
mImagesDir.mkdir();
- mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
- mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
- mPhotoSize = getPhotoSize(activity);
- mImageView.setOnClickListener(v -> showUpdatePhotoPopup());
+ mImageView = view;
+ mImageView.setOnClickListener(v -> showAvatarPicker());
mNewUserPhotoBitmap = bitmap;
}
@@ -125,32 +84,19 @@
if (resultCode != Activity.RESULT_OK) {
return false;
}
- final Uri pictureUri = data != null && data.getData() != null
- ? data.getData() : mTakePictureUri;
- // Check if the result is a content uri
- if (!ContentResolver.SCHEME_CONTENT.equals(pictureUri.getScheme())) {
- Log.e(TAG, "Invalid pictureUri scheme: " + pictureUri.getScheme());
- EventLog.writeEvent(0x534e4554, "172939189", -1, pictureUri.getPath());
- return false;
- }
+ if (requestCode == REQUEST_CODE_PICK_AVATAR) {
+ if (data.hasExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR)) {
+ int tintColor =
+ data.getIntExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR, -1);
+ onDefaultIconSelected(tintColor);
+ return true;
+ }
+ if (data.getData() != null) {
+ onPhotoCropped(data.getData());
+ return true;
+ }
- switch (requestCode) {
- case REQUEST_CODE_CROP_PHOTO:
- onPhotoCropped(pictureUri);
- return true;
- case REQUEST_CODE_TAKE_PHOTO:
- case REQUEST_CODE_CHOOSE_PHOTO:
- if (mTakePictureUri.equals(pictureUri)) {
- if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
- cropPhoto();
- } else {
- onPhotoNotCropped(pictureUri);
- }
- } else {
- copyAndCropPhoto(pictureUri);
- }
- return true;
}
return false;
}
@@ -159,224 +105,60 @@
return mNewUserPhotoDrawable;
}
- private void showUpdatePhotoPopup() {
- final Context context = mImageView.getContext();
- final boolean canTakePhoto = PhotoCapabilityUtils.canTakePhoto(context);
- final boolean canChoosePhoto = PhotoCapabilityUtils.canChoosePhoto(context);
-
- if (!canTakePhoto && !canChoosePhoto) {
- return;
- }
-
- final List<EditUserPhotoController.RestrictedMenuItem> items = new ArrayList<>();
-
- if (canTakePhoto) {
- final String title = context.getString(R.string.user_image_take_photo);
- items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
- this::takePhoto));
- }
-
- if (canChoosePhoto) {
- final String title = context.getString(R.string.user_image_choose_photo);
- items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
- this::choosePhoto));
- }
-
- final ListPopupWindow listPopupWindow = new ListPopupWindow(context);
-
- listPopupWindow.setAnchorView(mImageView);
- listPopupWindow.setModal(true);
- listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
- listPopupWindow.setAdapter(new RestrictedPopupMenuAdapter(context, items));
-
- final int width = Math.max(mImageView.getWidth(), context.getResources()
- .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width));
- listPopupWindow.setWidth(width);
- listPopupWindow.setDropDownGravity(Gravity.START);
-
- listPopupWindow.setOnItemClickListener((parent, view, position, id) -> {
- listPopupWindow.dismiss();
- final RestrictedMenuItem item =
- (RestrictedMenuItem) parent.getAdapter().getItem(position);
- item.doAction();
- });
-
- listPopupWindow.show();
+ private void showAvatarPicker() {
+ Intent intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
+ intent.putExtra(AvatarPickerActivity.EXTRA_FILE_AUTHORITY, mFileAuthority);
+ mActivityStarter.startActivityForResult(intent, REQUEST_CODE_PICK_AVATAR);
}
- private void takePhoto() {
- Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
- appendOutputExtra(intent, mTakePictureUri);
- mActivityStarter.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
- }
+ private void onDefaultIconSelected(int tintColor) {
+ try {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ Drawable drawable =
+ UserIcons.getDefaultUserIconInColor(mActivity.getResources(), tintColor);
+ Bitmap bitmap = convertToBitmap(drawable,
+ (int) mActivity.getResources().getDimension(R.dimen.circle_avatar_size));
- private void choosePhoto() {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
- intent.setType("image/*");
- appendOutputExtra(intent, mTakePictureUri);
- mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
- }
-
- private void copyAndCropPhoto(final Uri pictureUri) {
- // TODO: Replace AsyncTask
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- final ContentResolver cr = mActivity.getContentResolver();
- try (InputStream in = cr.openInputStream(pictureUri);
- OutputStream out = cr.openOutputStream(mTakePictureUri)) {
- Streams.copy(in, out);
- } catch (IOException e) {
- Log.w(TAG, "Failed to copy photo", e);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
- cropPhoto();
- }
- }
- }.execute();
- }
-
- private void cropPhoto() {
- // TODO: Use a public intent, when there is one.
- Intent intent = new Intent("com.android.camera.action.CROP");
- intent.setDataAndType(mTakePictureUri, "image/*");
- appendOutputExtra(intent, mCropPictureUri);
- appendCropExtras(intent);
- if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
- try {
- StrictMode.disableDeathOnFileUriExposure();
- mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
- } finally {
- StrictMode.enableDeathOnFileUriExposure();
- }
- } else {
- onPhotoNotCropped(mTakePictureUri);
+ ThreadUtils.postOnMainThread(() -> onPhotoProcessed(bitmap));
+ }).get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "Error processing default icon", e);
}
}
- private void appendOutputExtra(Intent intent, Uri pictureUri) {
- intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
- intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_READ_URI_PERMISSION);
- intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
- }
-
- private void appendCropExtras(Intent intent) {
- intent.putExtra("crop", "true");
- intent.putExtra("scale", true);
- intent.putExtra("scaleUpIfNeeded", true);
- intent.putExtra("aspectX", 1);
- intent.putExtra("aspectY", 1);
- intent.putExtra("outputX", mPhotoSize);
- intent.putExtra("outputY", mPhotoSize);
+ private static Bitmap convertToBitmap(@NonNull Drawable icon, int size) {
+ Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ icon.setBounds(0, 0, size, size);
+ icon.draw(canvas);
+ return bitmap;
}
private void onPhotoCropped(final Uri data) {
- // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
- new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... params) {
- InputStream imageStream = null;
- try {
- imageStream = mActivity.getContentResolver()
- .openInputStream(data);
- return BitmapFactory.decodeStream(imageStream);
- } catch (FileNotFoundException fe) {
- Log.w(TAG, "Cannot find image file", fe);
- return null;
- } finally {
- if (imageStream != null) {
- try {
- imageStream.close();
- } catch (IOException ioe) {
- Log.w(TAG, "Cannot close image stream", ioe);
- }
+ ThreadUtils.postOnBackgroundThread(() -> {
+ InputStream imageStream = null;
+ Bitmap bitmap = null;
+ try {
+ imageStream = mActivity.getContentResolver()
+ .openInputStream(data);
+ bitmap = BitmapFactory.decodeStream(imageStream);
+ } catch (FileNotFoundException fe) {
+ Log.w(TAG, "Cannot find image file", fe);
+ } finally {
+ if (imageStream != null) {
+ try {
+ imageStream.close();
+ } catch (IOException ioe) {
+ Log.w(TAG, "Cannot close image stream", ioe);
}
}
}
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- onPhotoProcessed(bitmap);
-
+ if (bitmap != null) {
+ Bitmap finalBitmap = bitmap;
+ ThreadUtils.postOnMainThread(() -> onPhotoProcessed(finalBitmap));
}
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
- }
-
- private void onPhotoNotCropped(final Uri data) {
- // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
- new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... params) {
- // Scale and crop to a square aspect ratio
- Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
- Config.ARGB_8888);
- Canvas canvas = new Canvas(croppedImage);
- Bitmap fullImage;
- try {
- InputStream imageStream = mActivity.getContentResolver()
- .openInputStream(data);
- fullImage = BitmapFactory.decodeStream(imageStream);
- } catch (FileNotFoundException fe) {
- return null;
- }
- if (fullImage != null) {
- int rotation = getRotation(mActivity, data);
- final int squareSize = Math.min(fullImage.getWidth(),
- fullImage.getHeight());
- final int left = (fullImage.getWidth() - squareSize) / 2;
- final int top = (fullImage.getHeight() - squareSize) / 2;
-
- Matrix matrix = new Matrix();
- RectF rectSource = new RectF(left, top,
- left + squareSize, top + squareSize);
- RectF rectDest = new RectF(0, 0, mPhotoSize, mPhotoSize);
- matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
- matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
- canvas.drawBitmap(fullImage, matrix, new Paint());
- return croppedImage;
- } else {
- // Bah! Got nothin.
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- onPhotoProcessed(bitmap);
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
- }
-
- /**
- * Reads the image's exif data and determines the rotation degree needed to display the image
- * in portrait mode.
- */
- private int getRotation(Context context, Uri selectedImage) {
- int rotation = -1;
- try {
- InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
- ExifInterface exif = new ExifInterface(imageStream);
- rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
- } catch (IOException exception) {
- Log.e(TAG, "Error while getting rotation", exception);
- }
-
- switch (rotation) {
- case ExifInterface.ORIENTATION_ROTATE_90:
- return 90;
- case ExifInterface.ORIENTATION_ROTATE_180:
- return 180;
- case ExifInterface.ORIENTATION_ROTATE_270:
- return 270;
- default:
- return 0;
- }
+ });
}
private void onPhotoProcessed(Bitmap bitmap) {
@@ -386,29 +168,6 @@
.getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
mImageView.setImageDrawable(mNewUserPhotoDrawable);
}
- new File(mImagesDir, TAKE_PICTURE_FILE_NAME).delete();
- new File(mImagesDir, CROP_PICTURE_FILE_NAME).delete();
- }
-
- private static int getPhotoSize(Context context) {
- try (Cursor cursor = context.getContentResolver().query(
- DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
- new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
- if (cursor != null) {
- cursor.moveToFirst();
- return cursor.getInt(0);
- } else {
- return DEFAULT_PHOTO_SIZE;
- }
- }
- }
-
- private Uri createTempImageUri(Context context, String fileName, boolean purge) {
- final File fullPath = new File(mImagesDir, fileName);
- if (purge) {
- fullPath.delete();
- }
- return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
}
File saveNewUserPhotoBitmap() {
@@ -435,84 +194,4 @@
void removeNewUserPhotoBitmapFile() {
new File(mImagesDir, NEW_USER_PHOTO_FILE_NAME).delete();
}
-
- private static final class RestrictedMenuItem {
- private final Context mContext;
- private final String mTitle;
- private final Runnable mAction;
- private final RestrictedLockUtils.EnforcedAdmin mAdmin;
- // Restriction may be set by system or something else via UserManager.setUserRestriction().
- private final boolean mIsRestrictedByBase;
-
- /**
- * The menu item, used for popup menu. Any element of such a menu can be disabled by admin.
- *
- * @param context A context.
- * @param title The title of the menu item.
- * @param restriction The restriction, that if is set, blocks the menu item.
- * @param action The action on menu item click.
- */
- RestrictedMenuItem(Context context, String title, String restriction,
- Runnable action) {
- mContext = context;
- mTitle = title;
- mAction = action;
-
- final int myUserId = UserHandle.myUserId();
- mAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
- restriction, myUserId);
- mIsRestrictedByBase = RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
- restriction, myUserId);
- }
-
- @Override
- public String toString() {
- return mTitle;
- }
-
- void doAction() {
- if (isRestrictedByBase()) {
- return;
- }
-
- if (isRestrictedByAdmin()) {
- RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mAdmin);
- return;
- }
-
- mAction.run();
- }
-
- boolean isRestrictedByAdmin() {
- return mAdmin != null;
- }
-
- boolean isRestrictedByBase() {
- return mIsRestrictedByBase;
- }
- }
-
- /**
- * Provide this adapter to ListPopupWindow.setAdapter() to have a popup window menu, where
- * any element can be restricted by admin (profile owner or device owner).
- */
- private static final class RestrictedPopupMenuAdapter extends ArrayAdapter<RestrictedMenuItem> {
- RestrictedPopupMenuAdapter(Context context, List<RestrictedMenuItem> items) {
- super(context, R.layout.restricted_popup_menu_item, R.id.text, items);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final View view = super.getView(position, convertView, parent);
- final RestrictedMenuItem item = getItem(position);
- final TextView text = (TextView) view.findViewById(R.id.text);
- final ImageView image = (ImageView) view.findViewById(R.id.restricted_icon);
-
- text.setEnabled(!item.isRestrictedByAdmin() && !item.isRestrictedByBase());
- image.setVisibility(item.isRestrictedByAdmin() && !item.isRestrictedByBase()
- ? ImageView.VISIBLE : ImageView.GONE);
-
- return view;
- }
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
index 165c280..b8615a7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
@@ -40,12 +40,12 @@
/**
* Check if the current user can perform any activity for
- * android.intent.action.GET_CONTENT action for images.
+ * ACTION_PICK_IMAGES action for images.
* Returns false if the device is currently locked and
* requires a PIN, pattern or password to unlock.
*/
public static boolean canChoosePhoto(Context context) {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.setType("image/*");
boolean canPerformActivityForGetImage =
context.getPackageManager().queryIntentActivities(intent, 0).size() > 0;
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 f167721..d7b366e 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 @@
private BluetoothDevice mDevice;
@Mock
private BluetoothA2dp mBluetoothA2dp;
+ @Mock
+ private BluetoothAdapter mBluetoothAdapter;
private BluetoothProfile.ServiceListener mServiceListener;
private A2dpProfile mProfile;
@@ -72,7 +74,8 @@
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 53d4653..86f7850 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 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 @@
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/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
index d6c8816..a5ee4c3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
@@ -62,7 +62,7 @@
@Mock
private ActivityStarter mActivityStarter;
- private boolean mCanChangePhoto;
+ private boolean mPhotoRestrictedByBase;
private Activity mActivity;
private TestEditUserInfoController mController;
@@ -85,8 +85,8 @@
}
@Override
- boolean canChangePhoto(Context context) {
- return mCanChangePhoto;
+ boolean isChangePhotoRestrictedByBase(Context context) {
+ return mPhotoRestrictedByBase;
}
}
@@ -96,7 +96,7 @@
mActivity = spy(ActivityController.of(new FragmentActivity()).get());
mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
mController = new TestEditUserInfoController();
- mCanChangePhoto = true;
+ mPhotoRestrictedByBase = true;
}
@Test
@@ -260,7 +260,7 @@
@Test
public void createDialog_canNotChangePhoto_nullPhotoController() {
- mCanChangePhoto = false;
+ mPhotoRestrictedByBase = false;
mController.createDialog(mActivity, mActivityStarter, mCurrentIcon,
"test", "title", null, null);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index dc7632d..b851232 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -46,7 +46,7 @@
import android.text.TextUtils;
import android.util.Log;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.RILConstants;
import com.android.internal.util.XmlUtils;
@@ -783,7 +783,7 @@
+ " VALUES(?,?);");
loadSetting(stmt, Global.SET_INSTALL_LOCATION, 0);
loadSetting(stmt, Global.DEFAULT_INSTALL_LOCATION,
- PackageHelper.APP_INSTALL_AUTO);
+ InstallLocationUtils.APP_INSTALL_AUTO);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
@@ -2534,7 +2534,7 @@
loadSetting(stmt, Settings.Global.SET_INSTALL_LOCATION, 0);
loadSetting(stmt, Settings.Global.DEFAULT_INSTALL_LOCATION,
- PackageHelper.APP_INSTALL_AUTO);
+ InstallLocationUtils.APP_INSTALL_AUTO);
// Set default cdma emergency tone
loadSetting(stmt, Settings.Global.EMERGENCY_TONE, 0);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 27fc6ba..ca90fbe 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -192,6 +192,9 @@
<uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS" />
<uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" />
<uses-permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS" />
+ <!-- Permission required for processes that don't own the focused window to switch
+ touch mode state -->
+ <uses-permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE" />
<!-- Permission required to test onPermissionsChangedListener -->
<uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" />
<uses-permission android:name="android.permission.SET_KEYBOARD_LAYOUT" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index f35f5dd..776a511 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -213,6 +213,9 @@
<!-- DevicePolicyManager get user restrictions -->
<uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" />
+ <!-- DevicePolicyManager get admin policy -->
+ <uses-permission android:name="android.permission.QUERY_ADMIN_POLICY" />
+
<!-- TV picture-in-picture -->
<uses-permission android:name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE" />
@@ -301,6 +304,11 @@
<!-- For clipboard overlay -->
<uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
+ <uses-permission android:name="android.permission.SET_CLIP_SOURCE" />
+
+ <!-- To change system language (HDMI CEC) -->
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+ <uses-permission android:name="android.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION" />
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
@@ -485,6 +493,16 @@
android:excludeFromRecents="true">
</activity>
+ <!-- started from HdmiCecLocalDevicePlayback -->
+ <activity android:name=".hdmi.HdmiCecSetMenuLanguageActivity"
+ android:exported="true"
+ android:launchMode="singleTop"
+ android:permission="android.permission.CHANGE_CONFIGURATION"
+ android:theme="@style/BottomSheet"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true">
+ </activity>
+
<!-- started from SensoryPrivacyService -->
<activity android:name=".sensorprivacy.SensorUseStartedActivity"
android:exported="true"
@@ -855,11 +873,11 @@
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 for external clients to notify us of nearby media devices -->
+ <!-- TODO(b/216313420): Export and guard with a permission. -->
<service
- android:name=".media.taptotransfer.sender.MediaTttSenderService"
- />
+ android:name=".media.nearby.NearbyMediaDevicesService"
+ />
<receiver
android:name=".tuner.TunerService$ClearReceiver"
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index dee4ff5..9722b1f 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -1,7 +1,7 @@
{
// Looking for unit test presubmit configuration?
// This currently lives in ATP config apct/system_ui/unit_test
- "presubmit": [
+ "presubmit-large": [
{
"name": "PlatformScenarioTests",
"options": [
@@ -24,7 +24,9 @@
"exclude-annotation": "android.platform.test.scenario.annotation.FoldableOnly"
}
]
- },
+ }
+ ],
+ "presubmit": [
{
"name": "SystemUIGoogleTests",
"options": [
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index ecb3cb3..339cab4 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 0000000..95bdd89
--- /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/action_chip_background.xml b/packages/SystemUI/res/drawable/action_chip_background.xml
index eeff39b..745470f 100644
--- a/packages/SystemUI/res/drawable/action_chip_background.xml
+++ b/packages/SystemUI/res/drawable/action_chip_background.xml
@@ -17,11 +17,11 @@
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:color="@color/screenshot_button_ripple">
+ android:color="@color/overlay_button_ripple">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<solid android:color="?androidprv:attr/colorAccentSecondary"/>
- <corners android:radius="@dimen/screenshot_button_corner_radius"/>
+ <corners android:radius="@dimen/overlay_button_corner_radius"/>
</shape>
</item>
</ripple>
diff --git a/packages/SystemUI/res/drawable/action_chip_container_background.xml b/packages/SystemUI/res/drawable/action_chip_container_background.xml
index 72767a1..36083f1 100644
--- a/packages/SystemUI/res/drawable/action_chip_container_background.xml
+++ b/packages/SystemUI/res/drawable/action_chip_container_background.xml
@@ -19,5 +19,5 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
<solid android:color="?androidprv:attr/colorSurface"/>
- <corners android:radius="@dimen/screenshot_action_container_corner_radius"/>
+ <corners android:radius="@dimen/overlay_action_container_corner_radius"/>
</shape>
diff --git a/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml b/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml
index c547c52..ec9465b 100644
--- a/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml
+++ b/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml
@@ -20,6 +20,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM12,15c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM14,6h-4L10,4h4v2z"
+ android:pathData="@*android:string/config_work_badge_path_24"
android:fillColor="?android:attr/colorAccent"/>
</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_next.xml b/packages/SystemUI/res/drawable/ic_media_next.xml
new file mode 100644
index 0000000..016653b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_next.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="12dp"
+ android:height="12dp"
+ android:viewportWidth="12"
+ android:viewportHeight="12">
+ <path
+ android:pathData="M0,12L8.5,6L0,0V12ZM2,3.86L5.03,6L2,8.14V3.86ZM12,0H10V12H12V0Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_media_pause.xml b/packages/SystemUI/res/drawable/ic_media_pause.xml
new file mode 100644
index 0000000..1f4b2cf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_pause.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14dp"
+ android:height="16dp"
+ android:viewportWidth="14"
+ android:viewportHeight="16">
+ <path
+ android:pathData="M9.1818,15.6363H13.5455V0.3635H9.1818V15.6363ZM0.4546,15.6363H4.8182V0.3635H0.4546V15.6363Z"
+ android:fillColor="#FFFFFF"
+ android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_play.xml b/packages/SystemUI/res/drawable/ic_media_play.xml
new file mode 100644
index 0000000..0eac1ad
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_play.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M20,12L6,21V3L20,12ZM15.26,12L8.55,7.68V16.32L15.26,12Z"
+ android:fillColor="#FFFFFF"
+ android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_prev.xml b/packages/SystemUI/res/drawable/ic_media_prev.xml
new file mode 100644
index 0000000..b4aeed4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_prev.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="12dp"
+ android:height="12dp"
+ android:viewportWidth="12"
+ android:viewportHeight="12">
+ <path
+ android:pathData="M0,0H2V12H0V0ZM3.5,6L12,12V0L3.5,6ZM6.97,6L10,8.14V3.86L6.97,6Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/drawable/overlay_actions_background_protection.xml
similarity index 91%
rename from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
rename to packages/SystemUI/res/drawable/overlay_actions_background_protection.xml
index dd818a0..d8f5632 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/packages/SystemUI/res/drawable/overlay_actions_background_protection.xml
@@ -17,6 +17,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<gradient
android:angle="90"
- android:startColor="@color/screenshot_background_protection_start"
+ android:startColor="@color/overlay_background_protection_start"
android:endColor="#00000000"/>
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshot_button_background.xml b/packages/SystemUI/res/drawable/overlay_button_background.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/screenshot_button_background.xml
rename to packages/SystemUI/res/drawable/overlay_button_background.xml
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 0000000..f54c30f
--- /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 0000000..1a323bc
--- /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/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/drawable/qs_media_scrim.xml
similarity index 62%
copy from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
copy to packages/SystemUI/res/drawable/qs_media_scrim.xml
index dd818a0..2ec319c 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/packages/SystemUI/res/drawable/qs_media_scrim.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2020 The Android Open Source Project
+ ~ 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.
@@ -12,11 +12,15 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ ~ limitations under the License
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/notification_corner_radius"/>
+ <!-- gradient from 25% in the center to 100% at edges -->
<gradient
- android:angle="90"
- android:startColor="@color/screenshot_background_protection_start"
- android:endColor="#00000000"/>
-</shape>
\ No newline at end of file
+ android:type="radial"
+ android:gradientRadius="100%p"
+ android:startColor="#40000000"
+ android:endColor="#FF000000" />
+</shape>
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 1e0ce00..0ff1db2 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -18,64 +18,72 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
- android:gravity="center_horizontal"
- android:elevation="@dimen/biometric_dialog_elevation">
+ android:elevation="@dimen/biometric_dialog_elevation"
+ android:orientation="vertical">
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <TextView
- android:id="@+id/title"
+ <RelativeLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Title"/>
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Subtitle"/>
+ <LinearLayout
+ android:id="@+id/auth_credential_header"
+ style="@style/AuthCredentialHeaderStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true">
- <TextView
- android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Description"/>
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:contentDescription="@null" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.AuthNonBioCredential.Title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <ImeAwareEditText
- android:id="@+id/lockPassword"
- android:layout_width="208dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:minHeight="48dp"
- android:gravity="center"
- android:inputType="textPassword"
- android:maxLength="500"
- android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
- style="@style/TextAppearance.AuthCredential.PasswordEntry"/>
+ <TextView
+ android:id="@+id/subtitle"
+ style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <TextView
- android:id="@+id/error"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Error"/>
+ <TextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.AuthNonBioCredential.Description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="5"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:layout_alignParentBottom="true">
+
+ <ImeAwareEditText
+ android:id="@+id/lockPassword"
+ style="@style/TextAppearance.AuthCredential.PasswordEntry"
+ android:layout_width="208dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+ android:inputType="textPassword"
+ android:minHeight="48dp" />
+
+ <TextView
+ android:id="@+id/error"
+ style="@style/TextAppearance.AuthNonBioCredential.Error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ </RelativeLayout>
</com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 4939ea2..dada981 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -22,76 +22,81 @@
android:gravity="center_horizontal"
android:elevation="@dimen/biometric_dialog_elevation">
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <TextView
- android:id="@+id/title"
+ <RelativeLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Title"/>
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Subtitle"/>
+ <LinearLayout
+ android:id="@+id/auth_credential_header"
+ style="@style/AuthCredentialHeaderStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
- <TextView
- android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Description"/>
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:contentDescription="@null" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.AuthNonBioCredential.Title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:gravity="center"
- android:paddingLeft="0dp"
- android:paddingRight="0dp"
- android:paddingTop="0dp"
- android:paddingBottom="16dp"
- android:clipToPadding="false">
+ <TextView
+ android:id="@+id/subtitle"
+ style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1"
- style="@style/LockPatternContainerStyle">
+ <TextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.AuthNonBioCredential.Description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
- <com.android.internal.widget.LockPatternView
- android:id="@+id/lockPattern"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- style="@style/LockPatternStyleBiometricPrompt"/>
-
- </FrameLayout>
-
- <TextView
- android:id="@+id/error"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Error"/>
+ android:layout_below="@id/auth_credential_header"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:paddingBottom="16dp"
+ android:paddingTop="60dp">
- </LinearLayout>
+ <FrameLayout
+ style="@style/LockPatternContainerStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1">
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPattern"
+ style="@style/LockPatternStyle"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true">
+
+ <TextView
+ android:id="@+id/error"
+ style="@style/TextAppearance.AuthNonBioCredential.Error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ </RelativeLayout>
</com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 2a3761e..4817d45 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -17,6 +17,7 @@
<com.android.systemui.clipboardoverlay.DraggableConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:theme="@style/FloatingOverlay"
android:alpha="0"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -27,7 +28,7 @@
android:layout_width="0dp"
android:elevation="1dp"
android:background="@drawable/action_chip_container_background"
- android:layout_marginStart="@dimen/screenshot_action_container_margin_horizontal"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
app:layout_constraintBottom_toBottomOf="@+id/actions_container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/actions_container"
@@ -36,9 +37,9 @@
android:id="@+id/actions_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
- android:paddingEnd="@dimen/screenshot_action_container_padding_right"
- android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
+ android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+ android:paddingEnd="@dimen/overlay_action_container_padding_right"
+ android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
android:elevation="1dp"
android:scrollbars="none"
app:layout_constraintHorizontal_bias="0"
@@ -50,10 +51,11 @@
<LinearLayout
android:id="@+id/actions"
android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- <include layout="@layout/screenshot_action_chip"
+ android:layout_height="wrap_content"
+ android:animateLayoutChanges="true">
+ <include layout="@layout/overlay_action_chip"
android:id="@+id/remote_copy_chip"/>
- <include layout="@layout/screenshot_action_chip"
+ <include layout="@layout/overlay_action_chip"
android:id="@+id/edit_chip"/>
</LinearLayout>
</HorizontalScrollView>
@@ -64,7 +66,7 @@
android:layout_marginStart="@dimen/overlay_offset_x"
android:layout_marginBottom="@dimen/overlay_offset_y"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/actions_container_background"
android:elevation="@dimen/overlay_preview_elevation"
app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
diff --git a/packages/SystemUI/res/layout/controls_fullscreen.xml b/packages/SystemUI/res/layout/controls_fullscreen.xml
index 7fd029c..11a5665 100644
--- a/packages/SystemUI/res/layout/controls_fullscreen.xml
+++ b/packages/SystemUI/res/layout/controls_fullscreen.xml
@@ -35,7 +35,8 @@
android:layout_height="wrap_content"
android:clipChildren="false"
android:orientation="vertical"
- android:clipToPadding="false" />
+ android:clipToPadding="false"
+ android:paddingHorizontal="@dimen/controls_padding_horizontal" />
</com.android.systemui.globalactions.MinHeightScrollView>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
new file mode 100644
index 0000000..b6f516f
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<TextClock
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/date_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/dream_overlay_complication_clock_date_padding_left"
+ android:paddingBottom="@dimen/dream_overlay_complication_clock_date_padding_bottom"
+ android:gravity="center_horizontal"
+ android:textColor="@android:color/white"
+ android:shadowColor="@color/keyguard_shadow_color"
+ android:shadowRadius="?attr/shadowRadius"
+ android:format12Hour="EEE, MMM d"
+ android:format24Hour="EEE, MMM d"
+ android:textSize="@dimen/dream_overlay_complication_clock_date_text_size"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
new file mode 100644
index 0000000..a41d34f
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<TextClock
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/time_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/dream_overlay_complication_clock_time_padding_left"
+ android:fontFamily="sans-serif-thin"
+ android:textColor="@android:color/white"
+ android:format12Hour="h:mm"
+ android:format24Hour="kk:mm"
+ android:shadowColor="@color/keyguard_shadow_color"
+ android:shadowRadius="?attr/shadowRadius"
+ android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml b/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
new file mode 100644
index 0000000..08f0d67
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/weather_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/dream_overlay_complication_weather_padding_left"
+ android:paddingBottom="@dimen/dream_overlay_complication_weather_padding_bottom"
+ android:textColor="@android:color/white"
+ android:shadowColor="@color/keyguard_shadow_color"
+ android:shadowRadius="?attr/shadowRadius"
+ android:textSize="@dimen/dream_overlay_complication_weather_text_size"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
index 5135947..f4eb32f 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
@@ -20,30 +20,28 @@
android:id="@+id/dream_overlay_complications_layer"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <TextClock
- android:id="@+id/time_view"
+ <androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:fontFamily="sans-serif-thin"
- android:format12Hour="h:mm"
- android:format24Hour="kk:mm"
- android:shadowColor="#B2000000"
- android:shadowRadius="2.0"
- android:singleLine="true"
- android:textSize="72sp"
- app:layout_constraintBottom_toTopOf="@+id/date_view"
- app:layout_constraintStart_toStartOf="parent" />
- <TextClock
- android:id="@+id/date_view"
+ android:id="@+id/complication_top_guide"
+ app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_top_percent"
+ android:orientation="horizontal"/>
+ <androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:shadowColor="#B2000000"
- android:shadowRadius="2.0"
- android:format12Hour="EEE, MMM d"
- android:format24Hour="EEE, MMM d"
- android:singleLine="true"
- android:textSize="18sp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="@+id/time_view"
- app:layout_constraintStart_toStartOf="@+id/time_view" />
+ android:id="@+id/complication_end_guide"
+ app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_end_percent"
+ android:orientation="vertical"/>
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/complication_bottom_guide"
+ app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_bottom_percent"
+ android:orientation="horizontal"/>
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/complication_start_guide"
+ app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_start_percent"
+ android:orientation="vertical"/>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index 4929f50..3c2183d 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -25,7 +25,7 @@
android:id="@+id/dream_overlay_content"
android:layout_width="match_parent"
android:layout_height="0dp"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/dream_overlay_status_bar"
app:layout_constraintBottom_toBottomOf="parent" />
<com.android.systemui.dreams.DreamOverlayStatusBarView
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index 856697c..cdf6103 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -18,7 +18,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:background="?android:colorBackgroundFloating"
android:id="@+id/root"
android:layout_width="match_parent"
@@ -32,7 +31,7 @@
android:text="@string/save"
android:layout_marginStart="8dp"
android:layout_marginTop="@dimen/long_screenshot_action_bar_top_margin"
- android:background="@drawable/screenshot_button_background"
+ android:background="@drawable/overlay_button_background"
android:textColor="?android:textColorSecondary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -46,7 +45,7 @@
android:text="@android:string/cancel"
android:layout_marginStart="6dp"
android:layout_marginTop="@dimen/long_screenshot_action_bar_top_margin"
- android:background="@drawable/screenshot_button_background"
+ android:background="@drawable/overlay_button_background"
android:textColor="?android:textColorSecondary"
app:layout_constraintStart_toEndOf="@id/save"
app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index cc02fea..51d1608 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -28,6 +28,22 @@
android:background="@drawable/qs_media_background"
android:theme="@style/MediaPlayer">
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="184dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:translationZ="0dp"
+ android:id="@+id/album_art"
+ android:scaleType="centerCrop"
+ android:adjustViewBounds="true"
+ android:clipToOutline="true"
+ android:foreground="@drawable/qs_media_scrim"
+ android:background="@drawable/qs_media_scrim"
+ />
+
<androidx.constraintlayout.widget.Guideline
android:id="@+id/center_vertical_guideline"
android:layout_width="wrap_content"
@@ -281,6 +297,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
+ android:id="@+id/cancel_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
@@ -304,6 +321,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
+ android:id="@+id/dismiss_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index b546a9c..9471b9f 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -264,6 +264,7 @@
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
+ android:id="@+id/cancel_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
@@ -288,6 +289,7 @@
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
+ android:id="@+id/dismiss_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
diff --git a/packages/SystemUI/res/layout/screenshot_action_chip.xml b/packages/SystemUI/res/layout/overlay_action_chip.xml
similarity index 61%
rename from packages/SystemUI/res/layout/screenshot_action_chip.xml
rename to packages/SystemUI/res/layout/overlay_action_chip.xml
index b80469f..e0c20ff 100644
--- a/packages/SystemUI/res/layout/screenshot_action_chip.xml
+++ b/packages/SystemUI/res/layout/overlay_action_chip.xml
@@ -14,33 +14,34 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.screenshot.ScreenshotActionChip
+<com.android.systemui.screenshot.OverlayActionChip
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/screenshot_action_chip"
+ android:id="@+id/overlay_action_chip"
+ android:theme="@style/FloatingOverlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/screenshot_action_chip_margin_start"
- android:paddingVertical="@dimen/screenshot_action_chip_margin_vertical"
+ android:layout_marginStart="@dimen/overlay_action_chip_margin_start"
+ android:paddingVertical="@dimen/overlay_action_chip_margin_vertical"
android:layout_gravity="center"
android:gravity="center"
android:alpha="0.0">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
+ android:paddingVertical="@dimen/overlay_action_chip_padding_vertical"
android:background="@drawable/action_chip_background"
android:gravity="center">
<ImageView
- android:id="@+id/screenshot_action_chip_icon"
- android:tint="?android:attr/textColorPrimary"
- android:layout_width="@dimen/screenshot_action_chip_icon_size"
- android:layout_height="@dimen/screenshot_action_chip_icon_size"/>
+ android:id="@+id/overlay_action_chip_icon"
+ android:tint="?attr/overlayButtonTextColor"
+ android:layout_width="@dimen/overlay_action_chip_icon_size"
+ android:layout_height="@dimen/overlay_action_chip_icon_size"/>
<TextView
- android:id="@+id/screenshot_action_chip_text"
+ android:id="@+id/overlay_action_chip_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textSize="@dimen/screenshot_action_chip_text_size"
- android:textColor="?android:attr/textColorPrimary"/>
+ android:textSize="@dimen/overlay_action_chip_text_size"
+ android:textColor="?attr/overlayButtonTextColor"/>
</LinearLayout>
-</com.android.systemui.screenshot.ScreenshotActionChip>
+</com.android.systemui.screenshot.OverlayActionChip>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 5cd9e94..b6e3499 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 f5c6036..22abd0c 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 10a2f4c..2c29f07 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/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml
index 227212b..890dbe5 100644
--- a/packages/SystemUI/res/layout/screenshot.xml
+++ b/packages/SystemUI/res/layout/screenshot.xml
@@ -17,7 +17,7 @@
<com.android.systemui.screenshot.ScreenshotView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/screenshot_frame"
- android:theme="@style/Screenshot"
+ android:theme="@style/FloatingOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no">
@@ -30,11 +30,11 @@
android:importantForAccessibility="no"/>
<ImageView
android:id="@+id/screenshot_actions_background"
- android:layout_height="@dimen/screenshot_bg_protection_height"
+ android:layout_height="@dimen/overlay_bg_protection_height"
android:layout_width="match_parent"
android:layout_gravity="bottom"
android:alpha="0.0"
- android:src="@drawable/screenshot_actions_background_protection"/>
+ android:src="@drawable/overlay_actions_background_protection"/>
<ImageView
android:id="@+id/screenshot_flash"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 8f791c3..813bb60 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -26,7 +26,7 @@
android:layout_width="0dp"
android:elevation="1dp"
android:background="@drawable/action_chip_container_background"
- android:layout_marginStart="@dimen/screenshot_action_container_margin_horizontal"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
app:layout_constraintBottom_toBottomOf="@+id/screenshot_actions_container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/screenshot_actions_container"
@@ -35,9 +35,9 @@
android:id="@+id/screenshot_actions_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
- android:paddingEnd="@dimen/screenshot_action_container_padding_right"
- android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
+ android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+ android:paddingEnd="@dimen/overlay_action_container_padding_right"
+ android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
android:elevation="1dp"
android:scrollbars="none"
app:layout_constraintHorizontal_bias="0"
@@ -50,11 +50,11 @@
android:id="@+id/screenshot_actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
- <include layout="@layout/screenshot_action_chip"
+ <include layout="@layout/overlay_action_chip"
android:id="@+id/screenshot_share_chip"/>
- <include layout="@layout/screenshot_action_chip"
+ <include layout="@layout/overlay_action_chip"
android:id="@+id/screenshot_edit_chip"/>
- <include layout="@layout/screenshot_action_chip"
+ <include layout="@layout/overlay_action_chip"
android:id="@+id/screenshot_scroll_chip"
android:visibility="gone" />
</LinearLayout>
@@ -89,7 +89,7 @@
<ImageView
android:id="@+id/screenshot_preview"
android:visibility="invisible"
- android:layout_width="@dimen/screenshot_x_scale"
+ android:layout_width="@dimen/overlay_x_scale"
android:layout_margin="@dimen/overlay_border_width"
android:layout_height="wrap_content"
android:layout_gravity="center"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 2290964..39d7f4f 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -122,7 +122,7 @@
android:layout_marginTop="@dimen/notification_panel_margin_top"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
- android:layout_marginBottom="@dimen/close_handle_underlap"
+ android:layout_marginBottom="@dimen/notification_panel_margin_bottom"
android:importantForAccessibility="no"
systemui:layout_constraintStart_toStartOf="parent"
systemui:layout_constraintEnd_toEndOf="parent"
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index ac4dfd2..8c5006d 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -31,9 +31,6 @@
<!-- orientation of the dead zone when touches have recently occurred elsewhere on screen -->
<integer name="navigation_bar_deadzone_orientation">1</integer>
- <!-- Max number of columns for quick controls area -->
- <integer name="controls_max_columns">4</integer>
-
<!-- Max number of columns for power menu -->
<integer name="power_menu_max_columns">4</integer>
</resources>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index fc5edf3..9d24e9b 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -66,4 +66,6 @@
<dimen name="controls_management_favorites_top_margin">8dp</dimen>
<dimen name="wallet_card_carousel_container_top_margin">24dp</dimen>
+
+ <dimen name="large_dialog_width">348dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 3412722..b318bbc 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -58,9 +58,9 @@
<!-- The color of the text in the Global Actions menu -->
<color name="global_actions_alert_text">@color/GM2_red_300</color>
- <!-- Global screenshot actions -->
- <color name="screenshot_button_ripple">#42FFFFFF</color>
- <color name="screenshot_background_protection_start">#80000000</color> <!-- 50% black -->
+ <!-- Floating overlay actions -->
+ <color name="overlay_button_ripple">#42FFFFFF</color>
+ <color name="overlay_background_protection_start">#80000000</color> <!-- 50% black -->
<!-- Media -->
<color name="media_divider">#85ffffff</color>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 1f815b7..f7261e7 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -47,8 +47,8 @@
<item name="android:textColorSecondary">?android:attr/textColorPrimaryInverse</item>
</style>
- <style name="Screenshot" parent="@android:style/Theme.DeviceDefault.DayNight">
- <item name="android:textColorPrimary">?android:attr/textColorPrimaryInverse</item>
+ <style name="FloatingOverlay" parent="@android:style/Theme.DeviceDefault.DayNight">
+ <item name="overlayButtonTextColor">?android:attr/textColorPrimaryInverse</item>
</style>
<style name="Theme.PeopleTileConfigActivity" parent="@style/Theme.SystemUI">
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index dabc310..fe546f6 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -15,9 +15,6 @@
~ limitations under the License
-->
<resources>
- <!-- Max number of columns for quick controls area -->
- <integer name="controls_max_columns">2</integer>
-
<!-- The maximum number of rows in the QSPanel -->
<integer name="quick_settings_max_rows">3</integer>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 89d046b..c2cec52 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -26,6 +26,10 @@
keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
<dimen name="keyguard_clock_top_margin">8dp</dimen>
+ <dimen name="split_shade_notifications_scrim_margin_bottom">16dp</dimen>
+
+ <dimen name="notification_panel_margin_bottom">48dp</dimen>
+
<!-- Limit the TaskView to this percentage of the overall screen width (0.0 - 1.0) -->
<item name="controls_task_view_width_percentage" translatable="false" format="float" type="dimen">0.45</item>
<dimen name="controls_task_view_right_margin">8dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml
index 02fd25b..3c6a81e 100644
--- a/packages/SystemUI/res/values-sw600dp-port/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/config.xml
@@ -15,7 +15,6 @@
~ limitations under the License
-->
<resources>
-
<!-- The maximum number of tiles in the QuickQSPanel -->
<integer name="quick_qs_panel_max_tiles">6</integer>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index f5dc7e3e..1b8453a 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -29,9 +29,6 @@
<!-- orientation of the dead zone when touches have recently occurred elsewhere on screen -->
<integer name="navigation_bar_deadzone_orientation">0</integer>
- <!-- Max number of columns for quick controls area -->
- <integer name="controls_max_columns">4</integer>
-
<!-- How many lines to show in the security footer -->
<integer name="qs_security_footer_maxLines">1</integer>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 7d03301..a66ed15 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -69,5 +69,5 @@
<dimen name="qs_detail_margin_top">0dp</dimen>
<!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
- <dimen name="large_dialog_width">504dp</dimen>
+ <dimen name="large_dialog_width">472dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml
index ae89ef4..be34a48 100644
--- a/packages/SystemUI/res/values-sw720dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/config.xml
@@ -15,9 +15,6 @@
~ limitations under the License
-->
<resources>
- <!-- Max number of columns for quick controls area -->
- <integer name="controls_max_columns">2</integer>
-
<!-- The maximum number of rows in the QSPanel -->
<integer name="quick_settings_max_rows">3</integer>
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
similarity index 62%
copy from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
copy to packages/SystemUI/res/values-sw720dp-land/dimens.xml
index cb602d79..ae557c4 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -1,5 +1,7 @@
-/**
- * Copyright (c) 2021, The Android Open Source Project
+<?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.
@@ -12,8 +14,10 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
-
-package android.net;
-
-parcelable NetworkStateSnapshot;
+*/
+-->
+<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 1564ee8..95df594 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/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/values-w500dp/config.xml
similarity index 62%
copy from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
copy to packages/SystemUI/res/values-w500dp/config.xml
index dd818a0..ef499ff 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/packages/SystemUI/res/values-w500dp/config.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2020 The Android Open Source Project
+ ~ 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.
@@ -14,9 +13,8 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <gradient
- android:angle="90"
- android:startColor="@color/screenshot_background_protection_start"
- android:endColor="#00000000"/>
-</shape>
\ No newline at end of file
+
+<resources>
+ <!-- Max number of columns for quick controls area -->
+ <integer name="controls_max_columns">3</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/values-w850dp/config.xml
similarity index 62%
copy from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
copy to packages/SystemUI/res/values-w850dp/config.xml
index dd818a0..337ebe1 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/packages/SystemUI/res/values-w850dp/config.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2020 The Android Open Source Project
+ ~ 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.
@@ -14,9 +13,8 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <gradient
- android:angle="90"
- android:startColor="@color/screenshot_background_protection_start"
- android:endColor="#00000000"/>
-</shape>
\ No newline at end of file
+
+<resources>
+ <!-- Max number of columns for quick controls area -->
+ <integer name="controls_max_columns">4</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index db69924..e6ab0ff 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -85,7 +85,7 @@
Contract: Pixel with fillColor blended over backgroundColor blended over translucent should
equal to singleToneColor blended over translucent. -->
<declare-styleable name="TonedIcon">
- <attr name="backgroundColor" format="integer" />
+ <attr name="iconBackgroundColor" format="integer" />
<attr name="fillColor" format="integer" />
<attr name="singleToneColor" format="integer" />
<attr name="homeHandleColor" format="integer" />
@@ -204,5 +204,7 @@
<attr name="singleLineVerticalPadding" format="dimension" />
<attr name="textViewId" format="reference" />
</declare-styleable>
+
+ <attr name="overlayButtonTextColor" format="color" />
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 81e3e04..3ab569a 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -138,9 +138,9 @@
<color name="udfps_enroll_progress">#7DA7F1</color>
<color name="udfps_enroll_progress_help">#ffEE675C</color>
- <!-- Global screenshot actions -->
- <color name="screenshot_button_ripple">#1f000000</color>
- <color name="screenshot_background_protection_start">#40000000</color> <!-- 25% black -->
+ <!-- Floating overlay actions -->
+ <color name="overlay_button_ripple">#1f000000</color>
+ <color name="overlay_background_protection_start">#40000000</color> <!-- 25% black -->
<!-- GM2 colors -->
<color name="GM2_grey_100">#F1F3F4</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b35571c..dba7290 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -258,36 +258,36 @@
<!-- Dimensions related to screenshots -->
- <!-- The padding on the global screenshot background image -->
- <dimen name="screenshot_x_scale">80dp</dimen>
- <dimen name="screenshot_bg_protection_height">242dp</dimen>
- <dimen name="screenshot_action_container_corner_radius">18dp</dimen>
- <dimen name="screenshot_action_container_padding_vertical">4dp</dimen>
- <dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
- <dimen name="screenshot_action_container_padding_right">8dp</dimen>
- <!-- Radius of the chip background on global screenshot actions -->
- <dimen name="screenshot_button_corner_radius">8dp</dimen>
- <!-- Margin between successive chips -->
- <dimen name="screenshot_action_chip_margin_start">8dp</dimen>
- <!-- Padding to make tappable chip height 48dp (18+11+11+4+4) -->
- <dimen name="screenshot_action_chip_margin_vertical">4dp</dimen>
- <dimen name="screenshot_action_chip_padding_vertical">11dp</dimen>
- <dimen name="screenshot_action_chip_icon_size">18sp</dimen>
- <!-- Padding on each side of the icon for icon-only chips -->
- <dimen name="screenshot_action_chip_icon_only_padding_horizontal">14dp</dimen>
- <!-- Padding at the edges of the chip for icon-and-text chips -->
- <dimen name="screenshot_action_chip_padding_horizontal">12dp</dimen>
- <!-- Spacing between chip icon and chip text -->
- <dimen name="screenshot_action_chip_spacing">8dp</dimen>
- <dimen name="screenshot_action_chip_text_size">14sp</dimen>
- <dimen name="screenshot_dismissal_height_delta">80dp</dimen>
+
<dimen name="screenshot_crop_handle_thickness">3dp</dimen>
<dimen name="long_screenshot_action_bar_top_margin">8dp</dimen>
<!-- Dimensions shared between "overlays" (clipboard and screenshot preview UIs) -->
+ <!-- Constrained size of the floating overlay preview -->
+ <dimen name="overlay_x_scale">80dp</dimen>
+ <!-- Radius of the chip background on floating overlay actions -->
+ <dimen name="overlay_button_corner_radius">8dp</dimen>
+ <!-- Margin between successive chips -->
+ <dimen name="overlay_action_chip_margin_start">8dp</dimen>
+ <!-- Padding to make tappable chip height 48dp (18+11+11+4+4) -->
+ <dimen name="overlay_action_chip_margin_vertical">4dp</dimen>
+ <dimen name="overlay_action_chip_padding_vertical">11dp</dimen>
+ <dimen name="overlay_action_chip_icon_size">18sp</dimen>
+ <!-- Padding on each side of the icon for icon-only chips -->
+ <dimen name="overlay_action_chip_icon_only_padding_horizontal">14dp</dimen>
+ <!-- Padding at the edges of the chip for icon-and-text chips -->
+ <dimen name="overlay_action_chip_padding_horizontal">12dp</dimen>
+ <!-- Spacing between chip icon and chip text -->
+ <dimen name="overlay_action_chip_spacing">8dp</dimen>
+ <dimen name="overlay_action_chip_text_size">14sp</dimen>
<dimen name="overlay_offset_y">8dp</dimen>
<dimen name="overlay_offset_x">16dp</dimen>
<dimen name="overlay_preview_elevation">4dp</dimen>
+ <dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
+ <dimen name="overlay_bg_protection_height">242dp</dimen>
+ <dimen name="overlay_action_container_corner_radius">18dp</dimen>
+ <dimen name="overlay_action_container_padding_vertical">4dp</dimen>
+ <dimen name="overlay_action_container_padding_right">8dp</dimen>
<dimen name="overlay_dismiss_button_elevation">7dp</dimen>
<dimen name="overlay_dismiss_button_tappable_size">48dp</dimen>
<dimen name="overlay_dismiss_button_margin">8dp</dimen>
@@ -295,7 +295,7 @@
<!-- need a negative margin for some of the constraints. should be overlay_border_width * -1 -->
<dimen name="overlay_border_width_neg">-4dp</dimen>
- <dimen name="clipboard_preview_size">@dimen/screenshot_x_scale</dimen>
+ <dimen name="clipboard_preview_size">@dimen/overlay_x_scale</dimen>
<!-- The width of the view containing navigation buttons -->
@@ -311,9 +311,6 @@
<!-- Move the back button drawable for 3 button layout upwards in ime mode and in portrait -->
<dimen name="navbar_back_button_ime_offset">2dp</dimen>
- <!-- Amount of close_handle that will NOT overlap the notification list -->
- <dimen name="close_handle_underlap">32dp</dimen>
-
<!-- Height of the status bar header bar in the car setting. -->
<dimen name="car_status_bar_header_height">128dp</dimen>
@@ -330,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>
@@ -376,8 +373,12 @@
-->
<dimen name="nssl_split_shade_min_content_height">256dp</dimen>
- <!-- The bottom margin of the panel that holds the list of notifications. -->
- <dimen name="notification_panel_margin_bottom">0dp</dimen>
+ <dimen name="notification_panel_margin_bottom">32dp</dimen>
+
+ <!-- The bottom padding of the panel that holds the list of notifications. -->
+ <dimen name="notification_panel_padding_bottom">0dp</dimen>
+
+ <dimen name="split_shade_notifications_scrim_margin_bottom">0dp</dimen>
<dimen name="notification_panel_width">@dimen/match_parent</dimen>
@@ -490,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>
@@ -1035,6 +1036,7 @@
<dimen name="controls_header_bottom_margin">24dp</dimen>
<dimen name="controls_header_app_icon_size">24dp</dimen>
<dimen name="controls_top_margin">48dp</dimen>
+ <dimen name="controls_padding_horizontal">0dp</dimen>
<dimen name="control_header_text_size">20sp</dimen>
<dimen name="control_item_text_size">16sp</dimen>
<dimen name="control_menu_item_text_size">16sp</dimen>
@@ -1343,4 +1345,47 @@
<!-- Height of the area at the top of the dream overlay to allow dragging down the notifications
shade. -->
<dimen name="dream_overlay_notifications_drag_area_height">100dp</dimen>
+
+ <!-- Dream overlay complications related dimensions -->
+ <dimen name="dream_overlay_complication_clock_time_padding_left">50dp</dimen>
+ <dimen name="dream_overlay_complication_clock_time_text_size">72sp</dimen>
+ <dimen name="dream_overlay_complication_clock_date_padding_left">60dp</dimen>
+ <dimen name="dream_overlay_complication_clock_date_padding_bottom">50dp</dimen>
+ <dimen name="dream_overlay_complication_clock_date_text_size">18sp</dimen>
+ <dimen name="dream_overlay_complication_weather_padding_left">20dp</dimen>
+ <dimen name="dream_overlay_complication_weather_padding_bottom">50dp</dimen>
+ <dimen name="dream_overlay_complication_weather_text_size">18sp</dimen>
+
+ <!-- The position of the end guide, which dream overlay complications can align their start with
+ if their end is aligned with the parent end. Represented as the percentage over from the
+ start of the parent container. -->
+ <item name="dream_overlay_complication_guide_end_percent" format="float" type="dimen">
+ 0.75
+ </item>
+
+ <!-- The position of the start guide, which dream overlay complications can align their end to
+ if their start is aligned with the parent start. Represented as the percentage over from
+ the start of the parent container. -->
+ <item name="dream_overlay_complication_guide_start_percent" format="float" type="dimen">
+ 0.25
+ </item>
+
+ <!-- The position of the bottom guide, which dream overlay complications can align their top to
+ if their bottom is aligned with the parent bottom. Represented as the percentage over from
+ the top of the parent container. -->
+ <item name="dream_overlay_complication_guide_bottom_percent" format="float" type="dimen">
+ 0.90
+ </item>
+
+ <!-- The position of the top guide, which dream overlay complications can align their bottom to
+ if their top is aligned with the parent top. Represented as the percentage over from
+ the top of the parent container. -->
+ <item name="dream_overlay_complication_guide_top_percent" format="float" type="dimen">
+ 0.10
+ </item>
+
+ <!-- The percentage of the screen from which a swipe can start to reveal the bouncer. -->
+ <item name="dream_overlay_bouncer_start_region_screen_percentage" format="float" type="dimen">
+ .2
+ </item>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 75ae52c..4dca0b0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -123,6 +123,18 @@
<!-- Message of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
<string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.</string>
+ <!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=80] -->
+ <string name="hdmi_cec_set_menu_language_title">Do you want to change the system language to <xliff:g id="language" example="German">%1$s</xliff:g>?</string>
+
+ <!-- Description for the <Set Menu Language> confirmation dialog [CHAR LIMIT=NONE] -->
+ <string name="hdmi_cec_set_menu_language_description">System language change requested by another device</string>
+
+ <!-- Button label for accepting language change [CHAR LIMIT=25] -->
+ <string name="hdmi_cec_set_menu_language_accept">Change language</string>
+
+ <!-- Button label for declining language change [CHAR LIMIT=25] -->
+ <string name="hdmi_cec_set_menu_language_decline">Keep current language</string>
+
<!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=NONE] -->
<string name="wifi_debugging_title">Allow wireless debugging on this network?</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ac98739..590cc9b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -236,6 +236,41 @@
<item name="android:textColor">?android:attr/colorError</item>
</style>
+ <style name="TextAppearance.AuthNonBioCredential"
+ parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:accessibilityLiveRegion">polite</item>
+ <item name="android:textAlignment">gravity</item>
+ <item name="android:layout_gravity">top</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Title">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">20dp</item>
+ <item name="android:textSize">36sp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Subtitle">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">20dp</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Description">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">20dp</item>
+ <item name="android:textSize">16sp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Error">
+ <item name="android:paddingTop">6dp</item>
+ <item name="android:paddingBottom">18dp</item>
+ <item name="android:paddingHorizontal">24dp</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">?android:attr/colorError</item>
+ <item name="android:gravity">center</item>
+ </style>
+
<style name="TextAppearance.AuthCredential.PasswordEntry" parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:gravity">center</item>
<item name="android:singleLine">true</item>
@@ -243,6 +278,15 @@
<item name="android:textSize">24sp</item>
</style>
+ <style name="AuthCredentialHeaderStyle">
+ <item name="android:paddingStart">48dp</item>
+ <item name="android:paddingEnd">24dp</item>
+ <item name="android:paddingTop">28dp</item>
+ <item name="android:paddingBottom">20dp</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:layout_gravity">top</item>
+ </style>
+
<style name="DeviceManagementDialogTitle">
<item name="android:gravity">center</item>
<item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item>
@@ -307,9 +351,8 @@
<item name="android:maxWidth">420dp</item>
<item name="android:minHeight">0dp</item>
<item name="android:minWidth">0dp</item>
- <item name="android:paddingBottom">0dp</item>
- <item name="android:paddingHorizontal">44dp</item>
- <item name="android:paddingTop">0dp</item>
+ <item name="android:paddingHorizontal">60dp</item>
+ <item name="android:paddingBottom">40dp</item>
</style>
<style name="LockPatternStyle">
@@ -404,19 +447,19 @@
</style>
<style name="DualToneLightTheme">
- <item name="backgroundColor">@color/light_mode_icon_color_dual_tone_background</item>
+ <item name="iconBackgroundColor">@color/light_mode_icon_color_dual_tone_background</item>
<item name="fillColor">@color/light_mode_icon_color_dual_tone_fill</item>
<item name="singleToneColor">@color/light_mode_icon_color_single_tone</item>
<item name="homeHandleColor">@color/navigation_bar_home_handle_light_color</item>
</style>
<style name="DualToneDarkTheme">
- <item name="backgroundColor">@color/dark_mode_icon_color_dual_tone_background</item>
+ <item name="iconBackgroundColor">@color/dark_mode_icon_color_dual_tone_background</item>
<item name="fillColor">@color/dark_mode_icon_color_dual_tone_fill</item>
<item name="singleToneColor">@color/dark_mode_icon_color_single_tone</item>
<item name="homeHandleColor">@color/navigation_bar_home_handle_dark_color</item>
</style>
<style name="QSHeaderDarkTheme">
- <item name="backgroundColor">@color/dark_mode_qs_icon_color_dual_tone_background</item>
+ <item name="iconBackgroundColor">@color/dark_mode_qs_icon_color_dual_tone_background</item>
<item name="fillColor">@color/dark_mode_qs_icon_color_dual_tone_fill</item>
<item name="singleToneColor">@color/dark_mode_qs_icon_color_single_tone</item>
</style>
@@ -664,7 +707,9 @@
<item name="android:windowActivityTransitions">true</item>
</style>
- <style name="Screenshot" parent="@android:style/Theme.DeviceDefault.DayNight"/>
+ <style name="FloatingOverlay" parent="@android:style/Theme.DeviceDefault.DayNight">
+ <item name="overlayButtonTextColor">?android:attr/textColorPrimary</item>
+ </style>
<!-- Clipboard overlay's edit text activity. -->
<style name="EditTextActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
new file mode 100644
index 0000000..ffab3cd
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.animation
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import java.lang.ref.WeakReference
+
+/**
+ * Translates items away/towards the hinge when the device is opened/closed, according to the
+ * direction specified in [ViewIdToTranslate.direction], for a maximum of [translationMax] when
+ * progresses are 0.
+ */
+class UnfoldConstantTranslateAnimator(
+ private val viewsIdToTranslate: Set<ViewIdToTranslate>,
+ private val progressProvider: UnfoldTransitionProgressProvider
+) : TransitionProgressListener {
+
+ private var viewsToTranslate = listOf<ViewToTranslate>()
+ private lateinit var rootView: ViewGroup
+ private var translationMax = 0f
+
+ fun init(rootView: ViewGroup, translationMax: Float) {
+ this.rootView = rootView
+ this.translationMax = translationMax
+ progressProvider.addCallback(this)
+ }
+
+ override fun onTransitionStarted() {
+ registerViewsForAnimation(rootView, viewsIdToTranslate)
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ translateViews(progress)
+ }
+
+ override fun onTransitionFinished() {
+ translateViews(progress = 1f)
+ }
+
+ private fun translateViews(progress: Float) {
+ // progress == 0 -> -translationMax
+ // progress == 1 -> 0
+ val xTrans = (progress - 1f) * translationMax
+ viewsToTranslate.forEach { (view, direction, shouldBeAnimated) ->
+ if (shouldBeAnimated()) {
+ view.get()?.translationX = xTrans * direction.multiplier
+ }
+ }
+ }
+
+ /** Finds in [parent] all views specified by [ids] and register them for the animation. */
+ private fun registerViewsForAnimation(parent: ViewGroup, ids: Set<ViewIdToTranslate>) {
+ viewsToTranslate =
+ ids.mapNotNull { (id, dir, pred) ->
+ parent.findViewById<View>(id)?.let { view ->
+ ViewToTranslate(WeakReference(view), dir, pred)
+ }
+ }
+ }
+
+ /** Represents a view to animate. [rootView] should contain a view with [viewId] inside. */
+ data class ViewIdToTranslate(
+ val viewId: Int,
+ val direction: Direction,
+ val shouldBeAnimated: () -> Boolean = { true }
+ )
+
+ private data class ViewToTranslate(
+ val view: WeakReference<View>,
+ val direction: Direction,
+ val shouldBeAnimated: () -> Boolean
+ )
+
+ /** Direction of the animation. */
+ enum class Direction(val multiplier: Float) {
+ LEFT(-1f),
+ RIGHT(1f),
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
index 9010d51..fc6bb50 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
@@ -42,7 +42,9 @@
* are different than actual bounds (e.g. view container may
* have larger width than width of the items in the container)
*/
- private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {}
+ private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {},
+ /** Allows to set the alpha based on the progress. */
+ private val alphaProvider: AlphaProvider? = null
) : UnfoldTransitionProgressProvider.TransitionProgressListener {
private val screenSize = Point()
@@ -99,17 +101,27 @@
override fun onTransitionProgress(progress: Float) {
animatedViews.forEach {
- it.view.get()?.let { view ->
- translationApplier.apply(
- view = view,
- x = it.startTranslationX * (1 - progress),
- y = it.startTranslationY * (1 - progress)
- )
- }
+ it.applyTransition(progress)
+ it.applyAlpha(progress)
}
lastAnimationProgress = progress
}
+ private fun AnimatedView.applyTransition(progress: Float) {
+ view.get()?.let { view ->
+ translationApplier.apply(
+ view = view,
+ x = startTranslationX * (1 - progress),
+ y = startTranslationY * (1 - progress)
+ )
+ }
+ }
+
+ private fun AnimatedView.applyAlpha(progress: Float) {
+ if (alphaProvider == null) return
+ view.get()?.alpha = alphaProvider.getAlpha(progress)
+ }
+
private fun createAnimatedView(view: View): AnimatedView =
AnimatedView(view = WeakReference(view)).updateAnimatedView(view)
@@ -146,6 +158,13 @@
}
}
+ /** Allows to set a custom alpha based on the progress. */
+ interface AlphaProvider {
+
+ /** Returns the alpha views should have at a given progress. */
+ fun getAlpha(progress: Float): Float
+ }
+
/**
* Interface that allows to use custom logic to get the center of the view
*/
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalHost.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalHost.aidl
deleted file mode 100644
index b76be4f..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalHost.aidl
+++ /dev/null
@@ -1,33 +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.shared.communal;
-
-import com.android.systemui.shared.communal.ICommunalSource;
-
-/**
-* An interface, implemented by SystemUI, for hosting a shared, communal surface on the lock
-* screen. Clients declare themselves sources (as defined by ICommunalSource). ICommunalHost is
-* meant only for the input of said sources. The lifetime scope and interactions that follow after
-* are bound to source.
-*/
-oneway interface ICommunalHost {
- /**
- * Invoked to specify the CommunalSource that should be consulted for communal surfaces to be
- * displayed.
- */
- void setSource(in ICommunalSource source) = 1;
-}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSource.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSource.aidl
deleted file mode 100644
index 7ef403b..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSource.aidl
+++ /dev/null
@@ -1,34 +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.shared.communal;
-
-import com.android.systemui.shared.communal.ICommunalSurfaceCallback;
-
-/**
- * An interface, implemented by clients of CommunalHost, to provide communal surfaces for SystemUI.
- * The associated binder proxy will be retained by SystemUI and called on-demand when a communal
- * surface is needed (either new instantiation or update).
- */
-oneway interface ICommunalSource {
- /**
- * Called by the CommunalHost when a new communal surface is needed. The provided arguments
- * match the arguments necessary to construct a SurfaceControlViewHost for producing a
- * SurfacePackage to return.
- */
- void getCommunalSurface(in IBinder hostToken, in int width, in int height, in int displayId,
- in ICommunalSurfaceCallback callback) = 1;
-}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSurfaceCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSurfaceCallback.aidl
deleted file mode 100644
index 3d5998b..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSurfaceCallback.aidl
+++ /dev/null
@@ -1,30 +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.shared.communal;
-
-import android.view.SurfaceControlViewHost.SurfacePackage;
-
-/**
-* An interface for receiving the result of a surface request. ICommunalSurfaceCallback is
-* implemented by the CommunalHost (SystemUI) to process the results of a new communal surface.
-*/
-interface ICommunalSurfaceCallback {
- /**
- * Invoked when the CommunalSurface has generated the SurfacePackage to be displayed.
- */
- void onSurface(in SurfacePackage surfacePackage) = 1;
-}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesProvider.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesProvider.aidl
new file mode 100644
index 0000000..6db06f0
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesProvider.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.media;
+
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback;
+import com.android.systemui.shared.media.NearbyDevice;
+
+/**
+ * An interface that provides information about nearby devices that are able to play media.
+ *
+ * External clients will implement this interface and System UI will invoke it if it's passed to
+ * SystemUI via {@link INearbyMediaDevicesService.registerProvider}.
+ */
+interface INearbyMediaDevicesProvider {
+ /**
+ * Returns a list of nearby devices that are able to play media.
+ */
+ List<NearbyDevice> getCurrentNearbyDevices() = 1;
+
+ /**
+ * Registers a callback that will be notified each time the status of a nearby device changes.
+ */
+ oneway void registerNearbyDevicesCallback(in INearbyMediaDevicesUpdateCallback callback) = 2;
+
+ /**
+ * Unregisters a callback. See {@link registerNearbyDevicesCallback}.
+ */
+ oneway void unregisterNearbyDevicesCallback(in INearbyMediaDevicesUpdateCallback callback) = 3;
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesService.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesService.aidl
new file mode 100644
index 0000000..4f3e10d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesService.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.media;
+
+import com.android.systemui.shared.media.INearbyMediaDevicesProvider;
+
+/**
+ * An interface that can be invoked to notify System UI of nearby media devices.
+ *
+ * External clients wanting to notify System UI about the status of nearby media devices should
+ * implement {@link INearbyMediaDevicesProvider} and then register it with system UI using this
+ * service.
+ *
+ * System UI will implement this interface and external clients will invoke it.
+ */
+interface INearbyMediaDevicesService {
+ /** Registers a new provider. */
+ oneway void registerProvider(INearbyMediaDevicesProvider provider) = 1;
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesUpdateCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesUpdateCallback.aidl
new file mode 100644
index 0000000..a835f52
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesUpdateCallback.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.media;
+
+/**
+ * A callback used to notify implementors of changes in the status of nearby devices that are able
+ * to play media.
+ *
+ * External clients may allow registration of these callbacks and external clients will be
+ * responsible for notifying the callbacks appropriately. System UI is only a mediator between the
+ * external client and these callbacks.
+ */
+interface INearbyMediaDevicesUpdateCallback {
+ /** Unknown distance range. */
+ const int RANGE_UNKNOWN = 0;
+ /** Distance is very far away from the peer device. */
+ const int RANGE_FAR = 1;
+ /** Distance is relatively long from the peer device, typically a few meters. */
+ const int RANGE_LONG = 2;
+ /** Distance is close to the peer device, typically with one or two meter. */
+ const int RANGE_CLOSE = 3;
+ /** Distance is very close to the peer device, typically within one meter or less. */
+ const int RANGE_WITHIN_REACH = 4;
+
+ /** Invoked by external clients when media device changes are detected. */
+ oneway void nearbyDeviceUpdate(in String routeId, in int rangeZone) = 1;
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.aidl
similarity index 89%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
rename to packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.aidl
index 861a4ed..62b50ed 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package com.android.systemui.shared.media;
-parcelable DeviceInfo;
+parcelable NearbyDevice;
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt
rename to packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.kt
index 96b853f..9cab3ab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.nearby
+package com.android.systemui.shared.media
import android.os.Parcel
import android.os.Parcelable
@@ -26,14 +26,15 @@
* - [routeId] identifying the media route
* - [rangeZone] specifying how far away the device with the media route is from this device.
*/
-class NearbyDevice(parcel: Parcel) : Parcelable {
- var routeId: String? = null
+class NearbyDevice(
+ val routeId: String?,
@RangeZone val rangeZone: Int
+) : Parcelable {
- init {
- routeId = parcel.readString() ?: "unknown"
+ private constructor(parcel: Parcel) : this(
+ routeId = parcel.readString() ?: null,
rangeZone = parcel.readInt()
- }
+ )
override fun describeContents() = 0
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/RangeZone.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/media/RangeZone.kt
new file mode 100644
index 0000000..b5eaff6
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/RangeZone.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.media
+
+import androidx.annotation.IntDef
+import kotlin.annotation.AnnotationRetention
+
+@IntDef(
+ INearbyMediaDevicesUpdateCallback.RANGE_UNKNOWN,
+ INearbyMediaDevicesUpdateCallback.RANGE_FAR,
+ INearbyMediaDevicesUpdateCallback.RANGE_LONG,
+ INearbyMediaDevicesUpdateCallback.RANGE_CLOSE,
+ INearbyMediaDevicesUpdateCallback.RANGE_WITHIN_REACH
+)
+@Retention(AnnotationRetention.SOURCE)
+/** The various range zones a device can be in, in relation to the current device. */
+annotation class RangeZone
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 d41aaf3..0000000
--- 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 eb1c9d0..0000000
--- 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/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index cb25e1a..89d6fb5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -17,11 +17,13 @@
package com.android.keyguard
import android.content.Context
-import android.view.View
import android.view.ViewGroup
import com.android.systemui.R
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
import com.android.systemui.unfold.SysUIUnfoldScope
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import javax.inject.Inject
@@ -30,84 +32,37 @@
* the set of ids, which also dictact which direction to move and when, via a filter function.
*/
@SysUIUnfoldScope
-class KeyguardUnfoldTransition @Inject constructor(
- val context: Context,
- val unfoldProgressProvider: NaturalRotationUnfoldProgressProvider
+class KeyguardUnfoldTransition
+@Inject
+constructor(
+ private val context: Context,
+ unfoldProgressProvider: NaturalRotationUnfoldProgressProvider
) {
- companion object {
- final val LEFT = -1
- final val RIGHT = 1
- }
+ /** Certain views only need to move if they are not currently centered */
+ var statusViewCentered = false
private val filterSplitShadeOnly = { !statusViewCentered }
private val filterNever = { true }
- private val ids = setOf(
- Triple(R.id.keyguard_status_area, LEFT, filterNever),
- Triple(R.id.controls_button, LEFT, filterNever),
- Triple(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly),
- Triple(R.id.lockscreen_clock_view, LEFT, filterNever),
- Triple(R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
- Triple(R.id.wallet_button, RIGHT, filterNever)
- )
- private var parent: ViewGroup? = null
- private var views = listOf<Triple<View, Int, () -> Boolean>>()
- private var xTranslationMax = 0f
-
- /**
- * Certain views only need to move if they are not currently centered
- */
- var statusViewCentered = false
-
- init {
- unfoldProgressProvider.addCallback(
- object : TransitionProgressListener {
- override fun onTransitionStarted() {
- findViews()
- }
-
- override fun onTransitionProgress(progress: Float) {
- translateViews(progress)
- }
-
- override fun onTransitionFinished() {
- translateViews(1f)
- }
- }
- )
+ private val translateAnimator by lazy {
+ UnfoldConstantTranslateAnimator(
+ viewsIdToTranslate =
+ setOf(
+ ViewIdToTranslate(R.id.keyguard_status_area, LEFT, filterNever),
+ ViewIdToTranslate(R.id.controls_button, LEFT, filterNever),
+ ViewIdToTranslate(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly),
+ ViewIdToTranslate(R.id.lockscreen_clock_view, LEFT, filterNever),
+ ViewIdToTranslate(
+ R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
+ ViewIdToTranslate(R.id.wallet_button, RIGHT, filterNever)),
+ progressProvider = unfoldProgressProvider)
}
- /**
- * Relies on the [parent] to locate views to translate
- */
+ /** Relies on the [parent] to locate views to translate. */
fun setup(parent: ViewGroup) {
- this.parent = parent
- xTranslationMax = context.resources.getDimensionPixelSize(
- R.dimen.keyguard_unfold_translation_x).toFloat()
- }
-
- /**
- * Manually translate views based on set direction. At the moment
- * [UnfoldMoveFromCenterAnimator] exists but moves all views a dynamic distance
- * from their mid-point. This code instead will only ever translate by a fixed amount.
- */
- private fun translateViews(progress: Float) {
- val xTrans = progress * xTranslationMax - xTranslationMax
- views.forEach {
- (view, direction, pred) -> if (pred()) {
- view.setTranslationX(xTrans * direction)
- }
- }
- }
-
- private fun findViews() {
- parent?.let { p ->
- views = ids.mapNotNull {
- (id, direction, pred) -> p.findViewById<View>(id)?.let {
- Triple(it, direction, pred)
- }
- }
- }
+ val translationMax =
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat()
+ translateAnimator.init(parent, translationMax)
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f2d0427..cc10b02 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1196,6 +1196,21 @@
return fingerprintAllowed || faceAllowed;
}
+ /**
+ * Returns whether the user is unlocked with a biometric that is currently bypassing
+ * the lock screen.
+ */
+ public boolean getUserUnlockedWithBiometricAndIsBypassing(int userId) {
+ BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
+ BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
+ // fingerprint always bypasses
+ boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated
+ && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric);
+ boolean faceAllowed = face != null && face.mAuthenticated
+ && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric);
+ return fingerprintAllowed || faceAllowed && mKeyguardBypassController.canBypass();
+ }
+
public boolean getUserTrustIsManaged(int userId) {
return mUserTrustIsManaged.get(userId) && !isTrustDisabled(userId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 08ed24c..b32c2b6 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -49,6 +49,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.hdmi.HdmiCecSetMenuLanguageHelper;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -250,6 +251,7 @@
@Inject Lazy<LocationController> mLocationController;
@Inject Lazy<RotationLockController> mRotationLockController;
@Inject Lazy<ZenModeController> mZenModeController;
+ @Inject Lazy<HdmiCecSetMenuLanguageHelper> mHdmiCecSetMenuLanguageHelper;
@Inject Lazy<HotspotController> mHotspotController;
@Inject Lazy<CastController> mCastController;
@Inject Lazy<FlashlightController> mFlashlightController;
diff --git a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
index fdc3229..2b8d3e0 100644
--- a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
@@ -54,11 +54,11 @@
Utils.getThemeAttr(context, R.attr.lightIconTheme))
darkColor = Color(
Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.singleToneColor),
- Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.backgroundColor),
+ Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.iconBackgroundColor),
Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.fillColor))
lightColor = Color(
Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.singleToneColor),
- Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.backgroundColor),
+ Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.iconBackgroundColor),
Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.fillColor))
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 20d6e32..881e6a9 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -120,6 +120,13 @@
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT; // = 9
/**
+ * Action ID to send the KEYCODE_HEADSETHOOK KeyEvent, which is used to answer/hang up calls and
+ * play/stop media
+ */
+ private static final int SYSTEM_ACTION_ID_KEYCODE_HEADSETHOOK =
+ AccessibilityService.GLOBAL_ACTION_KEYCODE_HEADSETHOOK; // = 10
+
+ /**
* Action ID to trigger the accessibility button
*/
public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON =
@@ -137,6 +144,36 @@
public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
AccessibilityService.GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE; // 15
+ /**
+ * Action ID to trigger the dpad up button
+ */
+ private static final int SYSTEM_ACTION_ID_DPAD_UP =
+ AccessibilityService.GLOBAL_ACTION_DPAD_UP; // 16
+
+ /**
+ * Action ID to trigger the dpad down button
+ */
+ private static final int SYSTEM_ACTION_ID_DPAD_DOWN =
+ AccessibilityService.GLOBAL_ACTION_DPAD_DOWN; // 17
+
+ /**
+ * Action ID to trigger the dpad left button
+ */
+ private static final int SYSTEM_ACTION_ID_DPAD_LEFT =
+ AccessibilityService.GLOBAL_ACTION_DPAD_LEFT; // 18
+
+ /**
+ * Action ID to trigger the dpad right button
+ */
+ private static final int SYSTEM_ACTION_ID_DPAD_RIGHT =
+ AccessibilityService.GLOBAL_ACTION_DPAD_RIGHT; // 19
+
+ /**
+ * Action ID to trigger dpad center keyevent
+ */
+ private static final int SYSTEM_ACTION_ID_DPAD_CENTER =
+ AccessibilityService.GLOBAL_ACTION_DPAD_CENTER; // 20
+
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
private final SystemActionsBroadcastReceiver mReceiver;
@@ -222,10 +259,34 @@
R.string.accessibility_system_action_screenshot_label,
SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT);
+ RemoteAction actionHeadsetHook = createRemoteAction(
+ R.string.accessibility_system_action_headset_hook_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_HEADSET_HOOK);
+
RemoteAction actionAccessibilityShortcut = createRemoteAction(
R.string.accessibility_system_action_hardware_a11y_shortcut_label,
SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_SHORTCUT);
+ RemoteAction actionDpadUp = createRemoteAction(
+ R.string.accessibility_system_action_dpad_up_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_UP);
+
+ RemoteAction actionDpadDown = createRemoteAction(
+ R.string.accessibility_system_action_dpad_down_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_DOWN);
+
+ RemoteAction actionDpadLeft = createRemoteAction(
+ R.string.accessibility_system_action_dpad_left_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_LEFT);
+
+ RemoteAction actionDpadRight = createRemoteAction(
+ R.string.accessibility_system_action_dpad_right_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_RIGHT);
+
+ RemoteAction actionDpadCenter = createRemoteAction(
+ R.string.accessibility_system_action_dpad_center_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_CENTER);
+
mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
@@ -234,8 +295,14 @@
mA11yManager.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG);
mA11yManager.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN);
mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
+ mA11yManager.registerSystemAction(actionHeadsetHook, SYSTEM_ACTION_ID_KEYCODE_HEADSETHOOK);
mA11yManager.registerSystemAction(
actionAccessibilityShortcut, SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT);
+ mA11yManager.registerSystemAction(actionDpadUp, SYSTEM_ACTION_ID_DPAD_UP);
+ mA11yManager.registerSystemAction(actionDpadDown, SYSTEM_ACTION_ID_DPAD_DOWN);
+ mA11yManager.registerSystemAction(actionDpadLeft, SYSTEM_ACTION_ID_DPAD_LEFT);
+ mA11yManager.registerSystemAction(actionDpadRight, SYSTEM_ACTION_ID_DPAD_RIGHT);
+ mA11yManager.registerSystemAction(actionDpadCenter, SYSTEM_ACTION_ID_DPAD_CENTER);
registerOrUnregisterDismissNotificationShadeAction();
}
@@ -305,6 +372,10 @@
labelId = R.string.accessibility_system_action_screenshot_label;
intent = SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT;
break;
+ case SYSTEM_ACTION_ID_KEYCODE_HEADSETHOOK:
+ labelId = R.string.accessibility_system_action_headset_hook_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_HEADSET_HOOK;
+ break;
case SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON:
labelId = R.string.accessibility_system_action_on_screen_a11y_shortcut_label;
intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON;
@@ -323,6 +394,26 @@
intent = SystemActionsBroadcastReceiver
.INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE;
break;
+ case SYSTEM_ACTION_ID_DPAD_UP:
+ labelId = R.string.accessibility_system_action_dpad_up_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_UP;
+ break;
+ case SYSTEM_ACTION_ID_DPAD_DOWN:
+ labelId = R.string.accessibility_system_action_dpad_down_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_DOWN;
+ break;
+ case SYSTEM_ACTION_ID_DPAD_LEFT:
+ labelId = R.string.accessibility_system_action_dpad_left_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_LEFT;
+ break;
+ case SYSTEM_ACTION_ID_DPAD_RIGHT:
+ labelId = R.string.accessibility_system_action_dpad_right_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_RIGHT;
+ break;
+ case SYSTEM_ACTION_ID_DPAD_CENTER:
+ labelId = R.string.accessibility_system_action_dpad_center_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_CENTER;
+ break;
default:
return;
}
@@ -411,6 +502,10 @@
SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
}
+ private void handleHeadsetHook() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK);
+ }
+
private void handleAccessibilityButton() {
AccessibilityManager.getInstance(mContext).notifyAccessibilityButtonClicked(
Display.DEFAULT_DISPLAY);
@@ -434,6 +529,26 @@
CommandQueue.FLAG_EXCLUDE_NONE, false /* force */));
}
+ private void handleDpadUp() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_UP);
+ }
+
+ private void handleDpadDown() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+
+ private void handleDpadLeft() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_LEFT);
+ }
+
+ private void handleDpadRight() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_RIGHT);
+ }
+
+ private void handleDpadCenter() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
+ }
+
private class SystemActionsBroadcastReceiver extends BroadcastReceiver {
private static final String INTENT_ACTION_BACK = "SYSTEM_ACTION_BACK";
private static final String INTENT_ACTION_HOME = "SYSTEM_ACTION_HOME";
@@ -443,6 +558,7 @@
private static final String INTENT_ACTION_POWER_DIALOG = "SYSTEM_ACTION_POWER_DIALOG";
private static final String INTENT_ACTION_LOCK_SCREEN = "SYSTEM_ACTION_LOCK_SCREEN";
private static final String INTENT_ACTION_TAKE_SCREENSHOT = "SYSTEM_ACTION_TAKE_SCREENSHOT";
+ private static final String INTENT_ACTION_HEADSET_HOOK = "SYSTEM_ACTION_HEADSET_HOOK";
private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON =
"SYSTEM_ACTION_ACCESSIBILITY_BUTTON";
private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER =
@@ -451,6 +567,11 @@
"SYSTEM_ACTION_ACCESSIBILITY_SHORTCUT";
private static final String INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
"SYSTEM_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE";
+ private static final String INTENT_ACTION_DPAD_UP = "SYSTEM_ACTION_DPAD_UP";
+ private static final String INTENT_ACTION_DPAD_DOWN = "SYSTEM_ACTION_DPAD_DOWN";
+ private static final String INTENT_ACTION_DPAD_LEFT = "SYSTEM_ACTION_DPAD_LEFT";
+ private static final String INTENT_ACTION_DPAD_RIGHT = "SYSTEM_ACTION_DPAD_RIGHT";
+ private static final String INTENT_ACTION_DPAD_CENTER = "SYSTEM_ACTION_DPAD_CENTER";
private PendingIntent createPendingIntent(Context context, String intentAction) {
switch (intentAction) {
@@ -462,10 +583,16 @@
case INTENT_ACTION_POWER_DIALOG:
case INTENT_ACTION_LOCK_SCREEN:
case INTENT_ACTION_TAKE_SCREENSHOT:
+ case INTENT_ACTION_HEADSET_HOOK:
case INTENT_ACTION_ACCESSIBILITY_BUTTON:
case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER:
case INTENT_ACTION_ACCESSIBILITY_SHORTCUT:
- case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE: {
+ case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE:
+ case INTENT_ACTION_DPAD_UP:
+ case INTENT_ACTION_DPAD_DOWN:
+ case INTENT_ACTION_DPAD_LEFT:
+ case INTENT_ACTION_DPAD_RIGHT:
+ case INTENT_ACTION_DPAD_CENTER: {
Intent intent = new Intent(intentAction);
intent.setPackage(context.getPackageName());
return PendingIntent.getBroadcast(context, 0, intent,
@@ -487,10 +614,16 @@
intentFilter.addAction(INTENT_ACTION_POWER_DIALOG);
intentFilter.addAction(INTENT_ACTION_LOCK_SCREEN);
intentFilter.addAction(INTENT_ACTION_TAKE_SCREENSHOT);
+ intentFilter.addAction(INTENT_ACTION_HEADSET_HOOK);
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON);
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER);
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_SHORTCUT);
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
+ intentFilter.addAction(INTENT_ACTION_DPAD_UP);
+ intentFilter.addAction(INTENT_ACTION_DPAD_DOWN);
+ intentFilter.addAction(INTENT_ACTION_DPAD_LEFT);
+ intentFilter.addAction(INTENT_ACTION_DPAD_RIGHT);
+ intentFilter.addAction(INTENT_ACTION_DPAD_CENTER);
return intentFilter;
}
@@ -530,6 +663,10 @@
handleTakeScreenshot();
break;
}
+ case INTENT_ACTION_HEADSET_HOOK: {
+ handleHeadsetHook();
+ break;
+ }
case INTENT_ACTION_ACCESSIBILITY_BUTTON: {
handleAccessibilityButton();
break;
@@ -546,6 +683,26 @@
handleAccessibilityDismissNotificationShade();
break;
}
+ case INTENT_ACTION_DPAD_UP: {
+ handleDpadUp();
+ break;
+ }
+ case INTENT_ACTION_DPAD_DOWN: {
+ handleDpadDown();
+ break;
+ }
+ case INTENT_ACTION_DPAD_LEFT: {
+ handleDpadLeft();
+ break;
+ }
+ case INTENT_ACTION_DPAD_RIGHT: {
+ handleDpadRight();
+ break;
+ }
+ case INTENT_ACTION_DPAD_CENTER: {
+ handleDpadCenter();
+ break;
+ }
default:
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index b0f7e55..fe5e36e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -37,6 +37,7 @@
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
@@ -64,6 +65,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.concurrency.Execution;
@@ -96,6 +98,7 @@
private final Handler mHandler;
private final Execution mExecution;
private final CommandQueue mCommandQueue;
+ private final StatusBarStateController mStatusBarStateController;
private final ActivityTaskManager mActivityTaskManager;
@Nullable
private final FingerprintManager mFingerprintManager;
@@ -118,6 +121,7 @@
@Nullable private UdfpsController mUdfpsController;
@Nullable private IUdfpsHbmListener mUdfpsHbmListener;
@Nullable private SidefpsController mSidefpsController;
+ @Nullable private IBiometricContextListener mBiometricContextListener;
@VisibleForTesting
TaskStackListener mTaskStackListener;
@VisibleForTesting
@@ -130,7 +134,7 @@
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
- private SensorPrivacyManager mSensorPrivacyManager;
+ @NonNull private final SensorPrivacyManager mSensorPrivacyManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private class BiometricTaskStackListener extends TaskStackListener {
@@ -491,6 +495,7 @@
Provider<SidefpsController> sidefpsControllerFactory,
@NonNull DisplayManager displayManager,
WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull StatusBarStateController statusBarStateController,
@Main Handler handler) {
super(context);
mExecution = execution;
@@ -504,6 +509,7 @@
mSidefpsControllerFactory = sidefpsControllerFactory;
mWindowManager = windowManager;
mUdfpsEnrolledForUser = new SparseBooleanArray();
+
mOrientationListener = new BiometricDisplayListener(
context,
displayManager,
@@ -514,6 +520,14 @@
return Unit.INSTANCE;
});
+ mStatusBarStateController = statusBarStateController;
+ mStatusBarStateController.addCallback(new StatusBarStateController.StateListener() {
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ notifyDozeChanged(isDozing);
+ }
+ });
+
mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
int[] faceAuthLocation = context.getResources().getIntArray(
@@ -564,6 +578,22 @@
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
}
+ @Override
+ public void setBiometicContextListener(IBiometricContextListener listener) {
+ mBiometricContextListener = listener;
+ notifyDozeChanged(mStatusBarStateController.isDozing());
+ }
+
+ private void notifyDozeChanged(boolean isDozing) {
+ if (mBiometricContextListener != null) {
+ try {
+ mBiometricContextListener.onDozeChanged(isDozing);
+ } catch (RemoteException e) {
+ Log.w(TAG, "failed to notify initial doze state");
+ }
+ }
+ }
+
/**
* Stores the listener received from {@link com.android.server.display.DisplayModeDirector}.
*
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 0e1cd51..72b40d4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -63,7 +63,8 @@
mClipboardOverlayController =
new ClipboardOverlayController(mContext, new TimeoutHandler(mContext));
}
- mClipboardOverlayController.setClipData(mClipboardManager.getPrimaryClip());
+ mClipboardOverlayController.setClipData(
+ mClipboardManager.getPrimaryClip(), mClipboardManager.getPrimaryClipSource());
mClipboardOverlayController.setOnSessionCompleteListener(() -> {
// Session is complete, free memory until it's needed again.
mClipboardOverlayController = null;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index b6bcb87..8b549b4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -74,7 +74,7 @@
import com.android.internal.policy.PhoneWindow;
import com.android.systemui.R;
import com.android.systemui.screenshot.FloatingWindowUtil;
-import com.android.systemui.screenshot.ScreenshotActionChip;
+import com.android.systemui.screenshot.OverlayActionChip;
import com.android.systemui.screenshot.TimeoutHandler;
import java.io.IOException;
@@ -106,12 +106,12 @@
private final DraggableConstraintLayout mView;
private final ImageView mImagePreview;
private final TextView mTextPreview;
- private final ScreenshotActionChip mEditChip;
- private final ScreenshotActionChip mRemoteCopyChip;
+ private final OverlayActionChip mEditChip;
+ private final OverlayActionChip mRemoteCopyChip;
private final View mActionContainerBackground;
private final View mDismissButton;
private final LinearLayout mActionContainer;
- private final ArrayList<ScreenshotActionChip> mActionChips = new ArrayList<>();
+ private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
private Runnable mOnSessionCompleteListener;
@@ -182,6 +182,7 @@
withWindowAttached(() -> {
mWindow.setContentView(mView);
updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
+ mView.requestLayout();
mView.post(this::animateIn);
});
@@ -213,7 +214,7 @@
mContext.sendBroadcast(new Intent(COPY_OVERLAY_ACTION), SELF_PERMISSION);
}
- void setClipData(ClipData clipData) {
+ void setClipData(ClipData clipData, String clipSource) {
reset();
if (clipData == null || clipData.getItemCount() == 0) {
showTextPreview(mContext.getResources().getString(
@@ -221,7 +222,7 @@
} else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
ClipData.Item item = clipData.getItemAt(0);
if (item.getTextLinks() != null) {
- AsyncTask.execute(() -> classifyText(clipData.getItemAt(0)));
+ AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
}
showEditableText(item.getText());
} else if (clipData.getItemAt(0).getUri() != null) {
@@ -238,7 +239,7 @@
mOnSessionCompleteListener = runnable;
}
- private void classifyText(ClipData.Item item) {
+ private void classifyText(ClipData.Item item, String source) {
ArrayList<RemoteAction> actions = new ArrayList<>();
for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
TextClassification classification = mTextClassifier.classifyText(
@@ -246,21 +247,21 @@
actions.addAll(classification.getActions());
}
mView.post(() -> {
- for (ScreenshotActionChip chip : mActionChips) {
- mActionContainer.removeView(chip);
- }
- mActionChips.clear();
+ resetActionChips();
for (RemoteAction action : actions) {
- ScreenshotActionChip chip = constructActionChip(action);
- mActionContainer.addView(chip);
- mActionChips.add(chip);
+ Intent targetIntent = action.getActionIntent().getIntent();
+ if (!TextUtils.equals(source, targetIntent.getComponent().getPackageName())) {
+ OverlayActionChip chip = constructActionChip(action);
+ mActionContainer.addView(chip);
+ mActionChips.add(chip);
+ }
}
});
}
- private ScreenshotActionChip constructActionChip(RemoteAction action) {
- ScreenshotActionChip chip = (ScreenshotActionChip) LayoutInflater.from(mContext).inflate(
- R.layout.screenshot_action_chip, mActionContainer, false);
+ private OverlayActionChip constructActionChip(RemoteAction action) {
+ OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
+ R.layout.overlay_action_chip, mActionContainer, false);
chip.setText(action.getTitle());
chip.setIcon(action.getIcon(), false);
chip.setPendingIntent(action.getActionIntent(), this::animateOut);
@@ -340,7 +341,7 @@
mEditChip.setAlpha(1f);
ContentResolver resolver = mContext.getContentResolver();
try {
- int size = mContext.getResources().getDimensionPixelSize(R.dimen.screenshot_x_scale);
+ int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
// The width of the view is capped, height maintains aspect ratio, so allow it to be
// taller if needed.
Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
@@ -364,7 +365,7 @@
}
private void animateOut() {
- getExitAnimation().start();
+ mView.dismiss();
}
private ValueAnimator getEnterAnimation() {
@@ -400,28 +401,6 @@
return anim;
}
- private ValueAnimator getExitAnimation() {
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-
- anim.addUpdateListener(animation -> {
- mView.setAlpha(1 - animation.getAnimatedFraction());
- final View actionBackground = requireNonNull(
- mView.findViewById(R.id.actions_container_background));
- mView.setTranslationX(
- -animation.getAnimatedFraction() * actionBackground.getWidth() / 2);
- });
-
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- hideImmediate();
- }
- });
-
- return anim;
- }
-
private void hideImmediate() {
// Note this may be called multiple times if multiple dismissal events happen at the same
// time.
@@ -451,13 +430,17 @@
}
}
- private void reset() {
- mView.setTranslationX(0);
- mView.setAlpha(0);
- for (ScreenshotActionChip chip : mActionChips) {
+ private void resetActionChips() {
+ for (OverlayActionChip chip : mActionChips) {
mActionContainer.removeView(chip);
}
mActionChips.clear();
+ }
+
+ private void reset() {
+ mView.setTranslationX(0);
+ mView.setAlpha(0);
+ resetActionChips();
mTimeoutHandler.cancelTimeout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
index 6a4be6e..8843462 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
@@ -98,10 +98,23 @@
return mSwipeDetector.onTouchEvent(ev);
}
+ /**
+ * Dismiss the view, with animation controlled by SwipeDismissHandler
+ */
+ public void dismiss() {
+ mSwipeDismissHandler.dismiss();
+ }
+
+ /**
+ * Set the callback to be run after view is dismissed
+ */
public void setOnDismissCallback(Runnable callback) {
mOnDismiss = callback;
}
+ /**
+ * Set the callback to be run when the view is interacted with (e.g. tapped)
+ */
public void setOnInteractionCallback(Runnable callback) {
mOnInteraction = callback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index bce8784..1653e0a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import com.android.systemui.ForegroundServicesDialog;
+import com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity;
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.people.PeopleSpaceActivity;
import com.android.systemui.people.widget.LaunchConversationActivity;
@@ -120,4 +121,11 @@
@IntoMap
@ClassKey(TvUnblockSensorActivity.class)
public abstract Activity bindTvUnblockSensorActivity(TvUnblockSensorActivity activity);
+
+ /** Inject into HdmiCecSetMenuLanguageActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(HdmiCecSetMenuLanguageActivity.class)
+ public abstract Activity bindHdmiCecSetMenuLanguageActivity(
+ HdmiCecSetMenuLanguageActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 96e2302..ec2beb1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -26,10 +26,15 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.clipboardoverlay.ClipboardListener;
import com.android.systemui.dreams.DreamOverlayRegistrant;
+import com.android.systemui.dreams.SmartSpaceComplication;
+import com.android.systemui.dreams.complication.DreamClockDateComplication;
+import com.android.systemui.dreams.complication.DreamClockTimeComplication;
+import com.android.systemui.dreams.complication.DreamWeatherComplication;
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.log.SessionTracker;
+import com.android.systemui.media.dream.MediaDreamSentinel;
import com.android.systemui.media.systemsounds.HomeSoundEffectController;
import com.android.systemui.power.PowerUI;
import com.android.systemui.privacy.television.TvOngoingPrivacyChip;
@@ -218,4 +223,39 @@
@ClassKey(DreamOverlayRegistrant.class)
public abstract CoreStartable bindDreamOverlayRegistrant(
DreamOverlayRegistrant dreamOverlayRegistrant);
+
+ /** Inject into SmartSpaceComplication.Registrant */
+ @Binds
+ @IntoMap
+ @ClassKey(SmartSpaceComplication.Registrant.class)
+ public abstract CoreStartable bindSmartSpaceComplicationRegistrant(
+ SmartSpaceComplication.Registrant registrant);
+
+ /** Inject into MediaDreamSentinel. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaDreamSentinel.class)
+ public abstract CoreStartable bindMediaDreamSentinel(
+ MediaDreamSentinel sentinel);
+
+ /** Inject into DreamClockTimeComplication.Registrant */
+ @Binds
+ @IntoMap
+ @ClassKey(DreamClockTimeComplication.Registrant.class)
+ public abstract CoreStartable bindDreamClockTimeComplicationRegistrant(
+ DreamClockTimeComplication.Registrant registrant);
+
+ /** Inject into DreamClockDateComplication.Registrant */
+ @Binds
+ @IntoMap
+ @ClassKey(DreamClockDateComplication.Registrant.class)
+ public abstract CoreStartable bindDreamClockDateComplicationRegistrant(
+ DreamClockDateComplication.Registrant registrant);
+
+ /** Inject into DreamWeatherComplication.Registrant */
+ @Binds
+ @IntoMap
+ @ClassKey(DreamWeatherComplication.Registrant.class)
+ public abstract CoreStartable bindDreamWeatherComplicationRegistrant(
+ DreamWeatherComplication.Registrant registrant);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 3ee0cad..2160744 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -88,16 +88,20 @@
}
};
+ private DreamOverlayStateController mStateController;
+
@Inject
public DreamOverlayService(
Context context,
@Main Executor executor,
DreamOverlayComponent.Factory dreamOverlayComponentFactory,
+ DreamOverlayStateController stateController,
KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mExecutor = executor;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
+ mStateController = stateController;
final DreamOverlayComponent component =
dreamOverlayComponentFactory.create(mViewModelStore, mHost);
@@ -118,6 +122,7 @@
setCurrentState(Lifecycle.State.DESTROYED);
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
windowManager.removeView(mWindow.getDecorView());
+ mStateController.setOverlayActive(false);
super.onDestroy();
}
@@ -127,6 +132,7 @@
mExecutor.execute(() -> {
addOverlayWindowLocked(layoutParams);
setCurrentState(Lifecycle.State.RESUMED);
+ mStateController.setOverlayActive(true);
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index e838848..ac7457d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -16,6 +16,8 @@
package com.android.systemui.dreams;
+import android.util.Log;
+
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
@@ -30,6 +32,8 @@
import java.util.HashSet;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -41,6 +45,16 @@
@SysUISingleton
public class DreamOverlayStateController implements
CallbackController<DreamOverlayStateController.Callback> {
+ private static final String TAG = "DreamOverlayStateCtlr";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
+
+ private static final int OP_CLEAR_STATE = 1;
+ private static final int OP_SET_STATE = 2;
+
+ private int mState;
+
/**
* Callback for dream overlay events.
*/
@@ -50,11 +64,26 @@
*/
default void onComplicationsChanged() {
}
+
+ /**
+ * Called when the dream overlay state changes.
+ */
+ default void onStateChanged() {
+ }
+
+ /**
+ * Called when the available complication types changes.
+ */
+ default void onAvailableComplicationTypesChanged() {
+ }
}
private final Executor mExecutor;
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+ @Complication.ComplicationType
+ private int mAvailableComplicationTypes = Complication.COMPLICATION_TYPE_NONE;
+
private final Collection<Complication> mComplications = new HashSet();
@VisibleForTesting
@@ -89,7 +118,33 @@
* Returns collection of present {@link Complication}.
*/
public Collection<Complication> getComplications() {
- return Collections.unmodifiableCollection(mComplications);
+ return getComplications(true);
+ }
+
+ /**
+ * Returns collection of present {@link Complication}.
+ */
+ public Collection<Complication> getComplications(boolean filterByAvailability) {
+ return Collections.unmodifiableCollection(filterByAvailability
+ ? mComplications
+ .stream()
+ .filter(complication -> {
+ @Complication.ComplicationType
+ final int requiredTypes = complication.getRequiredTypeAvailability();
+
+ return requiredTypes == Complication.COMPLICATION_TYPE_NONE
+ || (requiredTypes & getAvailableComplicationTypes()) == requiredTypes;
+ })
+ .collect(Collectors.toCollection(HashSet::new))
+ : mComplications);
+ }
+
+ private void notifyCallbacks(Consumer<Callback> callbackConsumer) {
+ mExecutor.execute(() -> {
+ for (Callback callback : mCallbacks) {
+ callbackConsumer.accept(callback);
+ }
+ });
}
@Override
@@ -117,4 +172,58 @@
mCallbacks.remove(callback);
});
}
+
+ /**
+ * Returns whether the overlay is active.
+ * @return {@code true} if overlay is active, {@code false} otherwise.
+ */
+ public boolean isOverlayActive() {
+ return containsState(STATE_DREAM_OVERLAY_ACTIVE);
+ }
+
+ private boolean containsState(int state) {
+ return (mState & state) != 0;
+ }
+
+ private void modifyState(int op, int state) {
+ final int existingState = mState;
+ switch (op) {
+ case OP_CLEAR_STATE:
+ mState &= ~state;
+ break;
+ case OP_SET_STATE:
+ mState |= state;
+ break;
+ }
+
+ if (existingState != mState) {
+ notifyCallbacks(callback -> callback.onStateChanged());
+ }
+ }
+
+ /**
+ * Sets whether the overlay is active.
+ * @param active {@code true} if overlay is active, {@code false} otherwise.
+ */
+ public void setOverlayActive(boolean active) {
+ modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
+ }
+
+ /**
+ * Returns the available complication types.
+ */
+ @Complication.ComplicationType
+ public int getAvailableComplicationTypes() {
+ return mAvailableComplicationTypes;
+ }
+
+ /**
+ * Sets the available complication types for the dream overlay.
+ */
+ public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
+ mExecutor.execute(() -> {
+ mAvailableComplicationTypes = types;
+ mCallbacks.forEach(callback -> callback.onAvailableComplicationTypesChanged());
+ });
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
new file mode 100644
index 0000000..09221b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
@@ -0,0 +1,113 @@
+/*
+ * 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.dreams;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+
+import javax.inject.Inject;
+
+/**
+ * {@link SmartSpaceComplication} embodies the SmartSpace view found on the lockscreen as a
+ * {@link Complication}
+ */
+public class SmartSpaceComplication implements Complication {
+ /**
+ * {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with
+ * SystemUI.
+ */
+ public static class Registrant extends CoreStartable {
+ private final LockscreenSmartspaceController mSmartSpaceController;
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final SmartSpaceComplication mComplication;
+
+ /**
+ * Default constructor for {@link SmartSpaceComplication}.
+ */
+ @Inject
+ public Registrant(Context context,
+ DreamOverlayStateController dreamOverlayStateController,
+ SmartSpaceComplication smartSpaceComplication,
+ LockscreenSmartspaceController smartSpaceController) {
+ super(context);
+ mDreamOverlayStateController = dreamOverlayStateController;
+ mComplication = smartSpaceComplication;
+ mSmartSpaceController = smartSpaceController;
+ }
+
+ @Override
+ public void start() {
+ if (mSmartSpaceController.isEnabled()) {
+ mDreamOverlayStateController.addComplication(mComplication);
+ }
+ }
+ }
+
+ private static class SmartSpaceComplicationViewHolder implements ViewHolder {
+ private final LockscreenSmartspaceController mSmartSpaceController;
+ private final Context mContext;
+
+ protected SmartSpaceComplicationViewHolder(
+ Context context,
+ LockscreenSmartspaceController smartSpaceController) {
+ mSmartSpaceController = smartSpaceController;
+ mContext = context;
+ }
+
+ @Override
+ public View getView() {
+ final FrameLayout smartSpaceContainer = new FrameLayout(mContext);
+ smartSpaceContainer.addView(
+ mSmartSpaceController.buildAndConnectView(smartSpaceContainer),
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ return smartSpaceContainer;
+ }
+
+ @Override
+ public ComplicationLayoutParams getLayoutParams() {
+ return new ComplicationLayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0, true);
+ }
+ }
+
+ private final LockscreenSmartspaceController mSmartSpaceController;
+ private final Context mContext;
+
+ @Inject
+ public SmartSpaceComplication(Context context,
+ LockscreenSmartspaceController smartSpaceController) {
+ mContext = context;
+ mSmartSpaceController = smartSpaceController;
+ }
+
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return new SmartSpaceComplicationViewHolder(mContext, mSmartSpaceController);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 96cf50d..fe458f4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -154,6 +154,27 @@
int CATEGORY_SYSTEM = 1 << 1;
/**
+ * The type of dream complications which can be provided by a {@link Complication}.
+ */
+ @IntDef(prefix = {"COMPLICATION_TYPE_"}, flag = true, value = {
+ COMPLICATION_TYPE_NONE,
+ COMPLICATION_TYPE_TIME,
+ COMPLICATION_TYPE_DATE,
+ COMPLICATION_TYPE_WEATHER,
+ COMPLICATION_TYPE_AIR_QUALITY,
+ COMPLICATION_TYPE_CAST_INFO
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ComplicationType {}
+
+ int COMPLICATION_TYPE_NONE = 0;
+ int COMPLICATION_TYPE_TIME = 1;
+ int COMPLICATION_TYPE_DATE = 1 << 1;
+ int COMPLICATION_TYPE_WEATHER = 1 << 2;
+ int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3;
+ int COMPLICATION_TYPE_CAST_INFO = 1 << 4;
+
+ /**
* The {@link Host} interface specifies a way a {@link Complication} to communicate with its
* parent entity for information and actions.
*/
@@ -207,4 +228,15 @@
* @return a {@link ViewHolder} for this {@link Complication} instance.
*/
ViewHolder createView(ComplicationViewModel model);
+
+ /**
+ * Returns the types that must be present in order for this complication to participate on
+ * the dream overlay. By default, this method returns
+ * {@code Complication.COMPLICATION_TYPE_NONE} to indicate no types are required.
+ * @return
+ */
+ @Complication.ComplicationType
+ default int getRequiredTypeAvailability() {
+ return Complication.COMPLICATION_TYPE_NONE;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
index 76818fa..f6fe8d2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
@@ -42,6 +42,10 @@
setValue(mDreamOverlayStateController.getComplications());
}
+ @Override
+ public void onAvailableComplicationTypesChanged() {
+ setValue(mDreamOverlayStateController.getComplications());
+ }
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index cb24ae6..5223f37 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -25,10 +25,13 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Constraints;
+import com.android.systemui.R;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Named;
@@ -102,6 +105,8 @@
final int direction = getLayoutParams().getDirection();
+ final boolean snapsToGuide = getLayoutParams().snapsToGuide();
+
// If no parent, view is the anchor. In this case, it is given the highest priority for
// alignment. All alignment preferences are done in relation to the parent container.
final boolean isRoot = head == mView;
@@ -125,6 +130,11 @@
} else {
params.startToEnd = head.getId();
}
+ if (snapsToGuide
+ && (direction == ComplicationLayoutParams.DIRECTION_DOWN
+ || direction == ComplicationLayoutParams.DIRECTION_UP)) {
+ params.endToStart = R.id.complication_start_guide;
+ }
break;
case ComplicationLayoutParams.POSITION_TOP:
if (isRoot || direction != ComplicationLayoutParams.DIRECTION_DOWN) {
@@ -132,6 +142,11 @@
} else {
params.topToBottom = head.getId();
}
+ if (snapsToGuide
+ && (direction == ComplicationLayoutParams.DIRECTION_END
+ || direction == ComplicationLayoutParams.DIRECTION_START)) {
+ params.endToStart = R.id.complication_top_guide;
+ }
break;
case ComplicationLayoutParams.POSITION_BOTTOM:
if (isRoot || direction != ComplicationLayoutParams.DIRECTION_UP) {
@@ -139,6 +154,11 @@
} else {
params.bottomToTop = head.getId();
}
+ if (snapsToGuide
+ && (direction == ComplicationLayoutParams.DIRECTION_END
+ || direction == ComplicationLayoutParams.DIRECTION_START)) {
+ params.topToBottom = R.id.complication_bottom_guide;
+ }
break;
case ComplicationLayoutParams.POSITION_END:
if (isRoot || direction != ComplicationLayoutParams.DIRECTION_START) {
@@ -146,6 +166,11 @@
} else {
params.endToStart = head.getId();
}
+ if (snapsToGuide
+ && (direction == ComplicationLayoutParams.DIRECTION_UP
+ || direction == ComplicationLayoutParams.DIRECTION_DOWN)) {
+ params.startToEnd = R.id.complication_end_guide;
+ }
break;
}
});
@@ -153,6 +178,16 @@
mView.setLayoutParams(params);
}
+ private void setGuide(ConstraintLayout.LayoutParams lp, int validDirections,
+ Consumer<ConstraintLayout.LayoutParams> consumer) {
+ final ComplicationLayoutParams layoutParams = getLayoutParams();
+ if (!layoutParams.snapsToGuide()) {
+ return;
+ }
+
+ consumer.accept(lp);
+ }
+
/**
* Informs the {@link ViewEntry}'s parent entity to remove the {@link ViewEntry} from
* being shown further.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index f9a69fa..8e8cb72 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -40,13 +40,13 @@
@interface Position {}
/** Align view with the top of parent or bottom of preceding {@link Complication}. */
- static final int POSITION_TOP = 1 << 0;
+ public static final int POSITION_TOP = 1 << 0;
/** Align view with the bottom of parent or top of preceding {@link Complication}. */
- static final int POSITION_BOTTOM = 1 << 1;
+ public static final int POSITION_BOTTOM = 1 << 1;
/** Align view with the start of parent or end of preceding {@link Complication}. */
- static final int POSITION_START = 1 << 2;
+ public static final int POSITION_START = 1 << 2;
/** Align view with the end of parent or start of preceding {@link Complication}. */
- static final int POSITION_END = 1 << 3;
+ public static final int POSITION_END = 1 << 3;
private static final int FIRST_POSITION = POSITION_TOP;
private static final int LAST_POSITION = POSITION_END;
@@ -61,13 +61,13 @@
@interface Direction {}
/** Position view upward from position. */
- static final int DIRECTION_UP = 1 << 0;
+ public static final int DIRECTION_UP = 1 << 0;
/** Position view downward from position. */
- static final int DIRECTION_DOWN = 1 << 1;
+ public static final int DIRECTION_DOWN = 1 << 1;
/** Position view towards the start of the parent. */
- static final int DIRECTION_START = 1 << 2;
+ public static final int DIRECTION_START = 1 << 2;
/** Position view towards the end of parent. */
- static final int DIRECTION_END = 1 << 3;
+ public static final int DIRECTION_END = 1 << 3;
@Position
private final int mPosition;
@@ -77,6 +77,8 @@
private final int mWeight;
+ private final boolean mSnapToGuide;
+
// Do not allow specifying opposite positions
private static final int[] INVALID_POSITIONS =
{ POSITION_BOTTOM | POSITION_TOP, POSITION_END | POSITION_START };
@@ -104,6 +106,27 @@
*/
public ComplicationLayoutParams(int width, int height, @Position int position,
@Direction int direction, int weight) {
+ this(width, height, position, direction, weight, false);
+ }
+
+ /**
+ * Constructs a {@link ComplicationLayoutParams}.
+ * @param width The width {@link android.view.View.MeasureSpec} for the view.
+ * @param height The height {@link android.view.View.MeasureSpec} for the view.
+ * @param position The place within the parent container where the view should be positioned.
+ * @param direction The direction the view should be laid out from either the parent container
+ * or preceding view.
+ * @param weight The weight that should be considered for this view when compared to other
+ * views. This has an impact on the placement of the view but not the rendering of
+ * the view.
+ * @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction
+ * will be automatically set to align with a predetermined guide for that
+ * side. For example, if the complication is aligned to the top end and
+ * direction is down, then the width of the complication will be set to span
+ * from the end of the parent to the guide.
+ */
+ public ComplicationLayoutParams(int width, int height, @Position int position,
+ @Direction int direction, int weight, boolean snapToGuide) {
super(width, height);
if (!validatePosition(position)) {
@@ -118,6 +141,8 @@
mDirection = direction;
mWeight = weight;
+
+ mSnapToGuide = snapToGuide;
}
/**
@@ -128,6 +153,7 @@
mPosition = source.mPosition;
mDirection = source.mDirection;
mWeight = source.mWeight;
+ mSnapToGuide = source.mSnapToGuide;
}
private static boolean validateDirection(@Position int position, @Direction int direction) {
@@ -180,7 +206,19 @@
return mPosition;
}
+ /**
+ * Returns the set weight for the complication. The weight determines ordering a complication
+ * given the same position/direction.
+ */
public int getWeight() {
return mWeight;
}
+
+ /**
+ * Returns whether the complication's dimension perpendicular to direction should be
+ * automatically set.
+ */
+ public boolean snapsToGuide() {
+ return mSnapToGuide;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
new file mode 100644
index 0000000..3a2a6ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
@@ -0,0 +1,53 @@
+/*
+ * 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.dreams.complication;
+
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_NONE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
+
+import com.android.settingslib.dream.DreamBackend;
+
+/**
+ * A collection of utility methods for working with {@link Complication}.
+ */
+public class ComplicationUtils {
+ /**
+ * Converts a {@link com.android.settingslib.dream.DreamBackend.ComplicationType} to
+ * {@link ComplicationType}.
+ */
+ @Complication.ComplicationType
+ public static int convertComplicationType(@DreamBackend.ComplicationType int type) {
+ switch (type) {
+ case DreamBackend.COMPLICATION_TYPE_TIME:
+ return COMPLICATION_TYPE_TIME;
+ case DreamBackend.COMPLICATION_TYPE_DATE:
+ return COMPLICATION_TYPE_DATE;
+ case DreamBackend.COMPLICATION_TYPE_WEATHER:
+ return COMPLICATION_TYPE_WEATHER;
+ case DreamBackend.COMPLICATION_TYPE_AIR_QUALITY:
+ return COMPLICATION_TYPE_AIR_QUALITY;
+ case DreamBackend.COMPLICATION_TYPE_CAST_INFO:
+ return COMPLICATION_TYPE_CAST_INFO;
+ default:
+ return COMPLICATION_TYPE_NONE;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
new file mode 100644
index 0000000..59c52b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
@@ -0,0 +1,106 @@
+/*
+ * 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.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationComponent.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationComponent.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_VIEW;
+
+import android.content.Context;
+import android.view.View;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationComponent;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Clock Date Complication that produce Clock Date view holder.
+ */
+public class DreamClockDateComplication implements Complication {
+ DreamClockDateComplicationComponent.Factory mComponentFactory;
+
+ /**
+ * Default constructor for {@link DreamClockDateComplication}.
+ */
+ @Inject
+ public DreamClockDateComplication(
+ DreamClockDateComplicationComponent.Factory componentFactory) {
+ mComponentFactory = componentFactory;
+ }
+
+ /**
+ * Create {@link DreamClockDateViewHolder}.
+ */
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return mComponentFactory.create().getViewHolder();
+ }
+
+ /**
+ * {@link CoreStartable} responsbile for registering {@link DreamClockDateComplication} with
+ * SystemUI.
+ */
+ public static class Registrant extends CoreStartable {
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final DreamClockDateComplication mComplication;
+
+ /**
+ * Default constructor to register {@link DreamClockDateComplication}.
+ */
+ @Inject
+ public Registrant(Context context,
+ DreamOverlayStateController dreamOverlayStateController,
+ DreamClockDateComplication dreamClockDateComplication) {
+ super(context);
+ mDreamOverlayStateController = dreamOverlayStateController;
+ mComplication = dreamClockDateComplication;
+ }
+
+ @Override
+ public void start() {
+ mDreamOverlayStateController.addComplication(mComplication);
+ }
+ }
+
+ /**
+ * ViewHolder to contain value/logic associated with a Clock Date Complication View.
+ */
+ public static class DreamClockDateViewHolder implements ViewHolder {
+ private final View mView;
+ private final ComplicationLayoutParams mLayoutParams;
+
+ @Inject
+ DreamClockDateViewHolder(@Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW) View view,
+ @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
+ ComplicationLayoutParams layoutParams) {
+ mView = view;
+ mLayoutParams = layoutParams;
+ }
+
+ @Override
+ public View getView() {
+ return mView;
+ }
+
+ @Override
+ public ComplicationLayoutParams getLayoutParams() {
+ return mLayoutParams;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
new file mode 100644
index 0000000..b0c3a42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
@@ -0,0 +1,106 @@
+/*
+ * 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.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
+
+import android.content.Context;
+import android.view.View;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationComponent;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Clock Time Complication that produce Clock Time view holder.
+ */
+public class DreamClockTimeComplication implements Complication {
+ DreamClockTimeComplicationComponent.Factory mComponentFactory;
+
+ /**
+ * Default constructor for {@link DreamClockTimeComplication}.
+ */
+ @Inject
+ public DreamClockTimeComplication(
+ DreamClockTimeComplicationComponent.Factory componentFactory) {
+ mComponentFactory = componentFactory;
+ }
+
+ /**
+ * Create {@link DreamClockTimeViewHolder}.
+ */
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return mComponentFactory.create().getViewHolder();
+ }
+
+ /**
+ * {@link CoreStartable} responsbile for registering {@link DreamClockTimeComplication} with
+ * SystemUI.
+ */
+ public static class Registrant extends CoreStartable {
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final DreamClockTimeComplication mComplication;
+
+ /**
+ * Default constructor to register {@link DreamClockTimeComplication}.
+ */
+ @Inject
+ public Registrant(Context context,
+ DreamOverlayStateController dreamOverlayStateController,
+ DreamClockTimeComplication dreamClockTimeComplication) {
+ super(context);
+ mDreamOverlayStateController = dreamOverlayStateController;
+ mComplication = dreamClockTimeComplication;
+ }
+
+ @Override
+ public void start() {
+ mDreamOverlayStateController.addComplication(mComplication);
+ }
+ }
+
+ /**
+ * ViewHolder to contain value/logic associated with a Clock Time Complication View.
+ */
+ public static class DreamClockTimeViewHolder implements ViewHolder {
+ private final View mView;
+ private final ComplicationLayoutParams mLayoutParams;
+
+ @Inject
+ DreamClockTimeViewHolder(@Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view,
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
+ ComplicationLayoutParams layoutParams) {
+ mView = view;
+ mLayoutParams = layoutParams;
+ }
+
+ @Override
+ public View getView() {
+ return mView;
+ }
+
+ @Override
+ public ComplicationLayoutParams getLayoutParams() {
+ return mLayoutParams;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java
new file mode 100644
index 0000000..cbdbef3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java
@@ -0,0 +1,180 @@
+/*
+ * 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.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_VIEW;
+
+import android.app.smartspace.SmartspaceAction;
+import android.app.smartspace.SmartspaceTarget;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.widget.TextView;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.R;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent;
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Weather Complication that produce Weather view holder.
+ */
+public class DreamWeatherComplication implements Complication {
+ DreamWeatherComplicationComponent.Factory mComponentFactory;
+
+ /**
+ * Default constructor for {@link DreamWeatherComplication}.
+ */
+ @Inject
+ public DreamWeatherComplication(
+ DreamWeatherComplicationComponent.Factory componentFactory) {
+ mComponentFactory = componentFactory;
+ }
+
+ /**
+ * Create {@link DreamWeatherViewHolder}.
+ */
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return mComponentFactory.create().getViewHolder();
+ }
+
+ /**
+ * {@link CoreStartable} for registering {@link DreamWeatherComplication} with SystemUI.
+ */
+ public static class Registrant extends CoreStartable {
+ private final LockscreenSmartspaceController mSmartSpaceController;
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final DreamWeatherComplication mComplication;
+
+ /**
+ * Default constructor to register {@link DreamWeatherComplication}.
+ */
+ @Inject
+ public Registrant(Context context,
+ LockscreenSmartspaceController smartspaceController,
+ DreamOverlayStateController dreamOverlayStateController,
+ DreamWeatherComplication dreamWeatherComplication) {
+ super(context);
+ mSmartSpaceController = smartspaceController;
+ mDreamOverlayStateController = dreamOverlayStateController;
+ mComplication = dreamWeatherComplication;
+ }
+
+ @Override
+ public void start() {
+ if (mSmartSpaceController.isEnabled()) {
+ mDreamOverlayStateController.addComplication(mComplication);
+ }
+ }
+ }
+
+ /**
+ * ViewHolder to contain value/logic associated with a Weather Complication View.
+ */
+ public static class DreamWeatherViewHolder implements ViewHolder {
+ private final TextView mView;
+ private final ComplicationLayoutParams mLayoutParams;
+ private final DreamWeatherViewController mViewController;
+
+ @Inject
+ DreamWeatherViewHolder(
+ @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view,
+ DreamWeatherViewController controller,
+ @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS)
+ ComplicationLayoutParams layoutParams) {
+ mView = view;
+ mLayoutParams = layoutParams;
+ mViewController = controller;
+ mViewController.init();
+ }
+
+ @Override
+ public TextView getView() {
+ return mView;
+ }
+
+ @Override
+ public ComplicationLayoutParams getLayoutParams() {
+ return mLayoutParams;
+ }
+ }
+
+ /**
+ * ViewController to contain value/logic associated with a Weather Complication View.
+ */
+ static class DreamWeatherViewController extends ViewController<TextView> {
+ private final LockscreenSmartspaceController mSmartSpaceController;
+ private SmartspaceTargetListener mSmartspaceTargetListener;
+
+ @Inject
+ DreamWeatherViewController(
+ @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view,
+ LockscreenSmartspaceController smartspaceController
+ ) {
+ super(view);
+ mSmartSpaceController = smartspaceController;
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mSmartspaceTargetListener = targets -> targets.forEach(
+ t -> {
+ if (t instanceof SmartspaceTarget
+ && ((SmartspaceTarget) t).getFeatureType()
+ == SmartspaceTarget.FEATURE_WEATHER) {
+ final SmartspaceTarget target = (SmartspaceTarget) t;
+ final SmartspaceAction headerAction = target.getHeaderAction();
+ if (headerAction == null || TextUtils.isEmpty(
+ headerAction.getTitle())) {
+ return;
+ }
+
+ String temperature = headerAction.getTitle().toString();
+ mView.setText(temperature);
+ final Icon icon = headerAction.getIcon();
+ if (icon != null) {
+ final int iconSize =
+ getResources().getDimensionPixelSize(
+ R.dimen.smart_action_button_icon_size);
+ final Drawable iconDrawable = icon.loadDrawable(getContext());
+ iconDrawable.setBounds(0, 0, iconSize, iconSize);
+ mView.setCompoundDrawables(iconDrawable, null, null, null);
+ mView.setCompoundDrawablePadding(
+ getResources().getDimensionPixelSize(
+ R.dimen.smart_action_button_icon_padding));
+
+ }
+ }
+ });
+ mSmartSpaceController.addListener(mSmartspaceTargetListener);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mSmartSpaceController.removeListener(mSmartspaceTargetListener);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationComponent.java
new file mode 100644
index 0000000..eaffb1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationComponent.java
@@ -0,0 +1,111 @@
+/*
+ * 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.dreams.complication.dagger;
+
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.DreamClockDateComplication.DreamClockDateViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link DreamClockDateComplicationComponent} is responsible for generating dependencies
+ * surrounding the
+ * Clock Date {@link com.android.systemui.dreams.complication.Complication}, such as the layout
+ * details.
+ */
+@Subcomponent(modules = {
+ DreamClockDateComplicationComponent.DreamClockDateComplicationModule.class,
+})
+@DreamClockDateComplicationComponent.DreamClockDateComplicationScope
+public interface DreamClockDateComplicationComponent {
+ /**
+ * Creates {@link DreamClockDateViewHolder}.
+ */
+ DreamClockDateViewHolder getViewHolder();
+
+ @Documented
+ @Retention(RUNTIME)
+ @Scope
+ @interface DreamClockDateComplicationScope {
+ }
+
+ /**
+ * Generates {@link DreamClockDateComplicationComponent}.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ DreamClockDateComplicationComponent create();
+ }
+
+ /**
+ * Scoped values for {@link DreamClockDateComplicationComponent}.
+ */
+ @Module
+ interface DreamClockDateComplicationModule {
+ String DREAM_CLOCK_DATE_COMPLICATION_VIEW = "clock_date_complication_view";
+ String DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS =
+ "clock_date_complication_layout_params";
+ // Order weight of insert into parent container
+ int INSERT_ORDER_WEIGHT = 2;
+
+ /**
+ * Provides the complication view.
+ */
+ @Provides
+ @DreamClockDateComplicationScope
+ @Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW)
+ static View provideComplicationView(LayoutInflater layoutInflater) {
+ return Preconditions.checkNotNull(
+ layoutInflater.inflate(R.layout.dream_overlay_complication_clock_date,
+ null, false),
+ "R.layout.dream_overlay_complication_clock_date did not properly inflated");
+ }
+
+ /**
+ * Provides the layout parameters for the complication view.
+ */
+ @Provides
+ @DreamClockDateComplicationScope
+ @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_END,
+ INSERT_ORDER_WEIGHT,
+ true);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationComponent.java
new file mode 100644
index 0000000..053c5b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationComponent.java
@@ -0,0 +1,111 @@
+/*
+ * 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.dreams.complication.dagger;
+
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.DreamClockTimeComplication.DreamClockTimeViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link DreamClockTimeComplicationComponent} is responsible for generating dependencies
+ * surrounding the
+ * Clock Time {@link com.android.systemui.dreams.complication.Complication}, such as the layout
+ * details.
+ */
+@Subcomponent(modules = {
+ DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.class,
+})
+@DreamClockTimeComplicationComponent.DreamClockTimeComplicationScope
+public interface DreamClockTimeComplicationComponent {
+ /**
+ * Creates {@link DreamClockTimeViewHolder}.
+ */
+ DreamClockTimeViewHolder getViewHolder();
+
+ @Documented
+ @Retention(RUNTIME)
+ @Scope
+ @interface DreamClockTimeComplicationScope {
+ }
+
+ /**
+ * Generates {@link DreamClockTimeComplicationComponent}.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ DreamClockTimeComplicationComponent create();
+ }
+
+ /**
+ * Scoped values for {@link DreamClockTimeComplicationComponent}.
+ */
+ @Module
+ interface DreamClockTimeComplicationModule {
+ String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view";
+ String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS =
+ "clock_time_complication_layout_params";
+ // Order weight of insert into parent container
+ int INSERT_ORDER_WEIGHT = 0;
+
+ /**
+ * Provides the complication view.
+ */
+ @Provides
+ @DreamClockTimeComplicationScope
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW)
+ static View provideComplicationView(LayoutInflater layoutInflater) {
+ return Preconditions.checkNotNull(
+ layoutInflater.inflate(R.layout.dream_overlay_complication_clock_time,
+ null, false),
+ "R.layout.dream_overlay_complication_clock_time did not properly inflated");
+ }
+
+ /**
+ * Provides the layout parameters for the complication view.
+ */
+ @Provides
+ @DreamClockTimeComplicationScope
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_UP,
+ INSERT_ORDER_WEIGHT,
+ true);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java
new file mode 100644
index 0000000..a282594
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java
@@ -0,0 +1,111 @@
+/*
+ * 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.dreams.complication.dagger;
+
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.DreamWeatherComplication.DreamWeatherViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link DreamWeatherComplicationComponent} is responsible for generating dependencies surrounding
+ * the
+ * Clock Date {@link com.android.systemui.dreams.complication.Complication}, such as the layout
+ * details.
+ */
+@Subcomponent(modules = {
+ DreamWeatherComplicationComponent.DreamWeatherComplicationModule.class,
+})
+@DreamWeatherComplicationComponent.DreamWeatherComplicationScope
+public interface DreamWeatherComplicationComponent {
+ /**
+ * Creates {@link DreamWeatherViewHolder}.
+ */
+ DreamWeatherViewHolder getViewHolder();
+
+ @Documented
+ @Retention(RUNTIME)
+ @Scope
+ @interface DreamWeatherComplicationScope {
+ }
+
+ /**
+ * Generates {@link DreamWeatherComplicationComponent}.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ DreamWeatherComplicationComponent create();
+ }
+
+ /**
+ * Scoped values for {@link DreamWeatherComplicationComponent}.
+ */
+ @Module
+ interface DreamWeatherComplicationModule {
+ String DREAM_WEATHER_COMPLICATION_VIEW = "weather_complication_view";
+ String DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS =
+ "weather_complication_layout_params";
+ // Order weight of insert into parent container
+ int INSERT_ORDER_WEIGHT = 1;
+
+ /**
+ * Provides the complication view.
+ */
+ @Provides
+ @DreamWeatherComplicationScope
+ @Named(DREAM_WEATHER_COMPLICATION_VIEW)
+ static TextView provideComplicationView(LayoutInflater layoutInflater) {
+ return Preconditions.checkNotNull((TextView)
+ layoutInflater.inflate(R.layout.dream_overlay_complication_weather,
+ null, false),
+ "R.layout.dream_overlay_complication_weather did not properly inflated");
+ }
+
+ /**
+ * Provides the layout parameters for the complication view.
+ */
+ @Provides
+ @DreamWeatherComplicationScope
+ @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_END,
+ INSERT_ORDER_WEIGHT,
+ true);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
new file mode 100644
index 0000000..8e4fb37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -0,0 +1,33 @@
+/*
+ * 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.dreams.complication.dagger;
+
+import com.android.systemui.dagger.SystemUIBinder;
+
+import dagger.Module;
+
+/**
+ * Module for all components with corresponding dream layer complications registered in
+ * {@link SystemUIBinder}.
+ */
+@Module(subcomponents = {
+ DreamClockTimeComplicationComponent.class,
+ DreamClockDateComplicationComponent.class,
+ DreamWeatherComplicationComponent.class,
+})
+public interface RegisteredComplicationsModule {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 3d2f924..d8af9e5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.dreams.dagger;
+import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule;
import com.android.systemui.dreams.touch.dagger.DreamTouchModule;
import dagger.Module;
@@ -25,6 +26,7 @@
*/
@Module(includes = {
DreamTouchModule.class,
+ RegisteredComplicationsModule.class,
},
subcomponents = {
DreamOverlayComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 503817a..4eb5cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -34,7 +34,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayContainerView;
import com.android.systemui.dreams.DreamOverlayStatusBarView;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
@@ -44,7 +43,6 @@
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
-import dagger.multibindings.IntoSet;
/** Dagger module for {@link DreamOverlayComponent}. */
@Module
@@ -149,12 +147,4 @@
static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) {
return lifecycleOwner.getLifecycle();
}
-
- // TODO: This stub should be removed once there is a {@link DreamTouchHandler}
- // implementation present.
- @Provides
- @IntoSet
- static DreamTouchHandler provideDreamTouchHandler() {
- return session -> { };
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
new file mode 100644
index 0000000..d16c8c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -0,0 +1,273 @@
+/*
+ * 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.dreams.touch;
+
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING;
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING;
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;
+
+import android.animation.ValueAnimator;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
+ */
+public class BouncerSwipeTouchHandler implements DreamTouchHandler {
+ /**
+ * An interface for creating ValueAnimators.
+ */
+ public interface ValueAnimatorCreator {
+ /**
+ * Creates {@link ValueAnimator}.
+ */
+ ValueAnimator create(float start, float finish);
+ }
+
+ /**
+ * An interface for obtaining VelocityTrackers.
+ */
+ public interface VelocityTrackerFactory {
+ /**
+ * Obtains {@link VelocityTracker}.
+ */
+ VelocityTracker obtain();
+ }
+
+ public static final float FLING_PERCENTAGE_THRESHOLD = 0.5f;
+
+ private static final String TAG = "BouncerSwipeTouchHandler";
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final float mBouncerZoneScreenPercentage;
+
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private float mCurrentExpansion;
+ private final StatusBar mStatusBar;
+
+ private VelocityTracker mVelocityTracker;
+
+ private final FlingAnimationUtils mFlingAnimationUtils;
+ private final FlingAnimationUtils mFlingAnimationUtilsClosing;
+
+ private Boolean mCapture;
+
+ private TouchSession mTouchSession;
+
+ private ValueAnimatorCreator mValueAnimatorCreator;
+
+ private VelocityTrackerFactory mVelocityTrackerFactory;
+
+ private final GestureDetector.OnGestureListener mOnGestureListener =
+ new GestureDetector.SimpleOnGestureListener() {
+ boolean mTrack;
+ boolean mBouncerPresent;
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ // We only consider gestures that originate from the lower portion of the
+ // screen.
+ final float displayHeight = mStatusBar.getDisplayHeight();
+
+ mBouncerPresent = mStatusBar.isBouncerShowing();
+
+ // The target zone is either at the top or bottom of the screen, dependent on
+ // whether the bouncer is present.
+ final float zonePercentage =
+ Math.abs(e.getY() - (mBouncerPresent ? 0 : displayHeight))
+ / displayHeight;
+
+ mTrack = zonePercentage < mBouncerZoneScreenPercentage;
+
+ // Never capture onDown. While this might lead to some false positive touches
+ // being sent to other windows/layers, this is necessary to make sure the
+ // proper touch event sequence is received by others in the event we do not
+ // consume the sequence here.
+ return false;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+ float distanceY) {
+ // Do not handle scroll gestures if not tracking touch events.
+ if (!mTrack) {
+ return false;
+ }
+
+ if (mCapture == null) {
+ // If the user scrolling favors a vertical direction, begin capturing
+ // scrolls.
+ mCapture = Math.abs(distanceY) > Math.abs(distanceX);
+
+ if (mCapture) {
+ // Since the user is dragging the bouncer up, set scrimmed to false.
+ mStatusBarKeyguardViewManager.showBouncer(false);
+ }
+ }
+
+ if (!mCapture) {
+ return false;
+ }
+
+ // For consistency, we adopt the expansion definition found in the
+ // PanelViewController. In this case, expansion refers to the view above the
+ // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
+ // is fully hidden at full expansion (1) and fully visible when fully collapsed
+ // (0).
+ final float screenTravelPercentage =
+ Math.abs((e1.getY() - e2.getY()) / mStatusBar.getDisplayHeight());
+ setPanelExpansion(
+ mBouncerPresent ? screenTravelPercentage : 1 - screenTravelPercentage);
+
+ return true;
+ }
+ };
+
+ private void setPanelExpansion(float expansion) {
+ mCurrentExpansion = expansion;
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(mCurrentExpansion, false, true);
+ }
+
+ @Inject
+ public BouncerSwipeTouchHandler(
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ StatusBar statusBar,
+ NotificationShadeWindowController notificationShadeWindowController,
+ ValueAnimatorCreator valueAnimatorCreator,
+ VelocityTrackerFactory velocityTrackerFactory,
+ @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+ FlingAnimationUtils flingAnimationUtils,
+ @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+ FlingAnimationUtils flingAnimationUtilsClosing,
+ @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage) {
+ mStatusBar = statusBar;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mNotificationShadeWindowController = notificationShadeWindowController;
+ mBouncerZoneScreenPercentage = swipeRegionPercentage;
+ mFlingAnimationUtils = flingAnimationUtils;
+ mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
+ mValueAnimatorCreator = valueAnimatorCreator;
+ mVelocityTrackerFactory = velocityTrackerFactory;
+ }
+
+ @Override
+ public void onSessionStart(TouchSession session) {
+ mVelocityTracker = mVelocityTrackerFactory.obtain();
+ mTouchSession = session;
+ mVelocityTracker.clear();
+ mNotificationShadeWindowController.setForcePluginOpen(true, this);
+ session.registerGestureListener(mOnGestureListener);
+ session.registerInputListener(ev -> onMotionEvent(ev));
+
+ }
+
+ @Override
+ public void onSessionEnd(TouchSession session) {
+ mVelocityTracker.recycle();
+ mCapture = null;
+ mNotificationShadeWindowController.setForcePluginOpen(false, this);
+ }
+
+ private void onMotionEvent(InputEvent event) {
+ if (!(event instanceof MotionEvent)) {
+ Log.e(TAG, "non MotionEvent received:" + event);
+ return;
+ }
+
+ final MotionEvent motionEvent = (MotionEvent) event;
+
+ switch(motionEvent.getAction()) {
+ case MotionEvent.ACTION_UP:
+ // If we are not capturing any input, there is no need to consider animating to
+ // finish transition.
+ if (mCapture == null || !mCapture) {
+ break;
+ }
+
+ // We must capture the resulting velocities as resetMonitor() will clear these
+ // values.
+ mVelocityTracker.computeCurrentVelocity(1000);
+ final float verticalVelocity = mVelocityTracker.getYVelocity();
+ final float horizontalVelocity = mVelocityTracker.getXVelocity();
+
+ final float velocityVector =
+ (float) Math.hypot(horizontalVelocity, verticalVelocity);
+
+
+ final float expansion = flingRevealsOverlay(verticalVelocity, velocityVector)
+ ? KeyguardBouncer.EXPANSION_HIDDEN : KeyguardBouncer.EXPANSION_VISIBLE;
+ flingToExpansion(verticalVelocity, expansion);
+
+ if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) {
+ mStatusBarKeyguardViewManager.reset(false);
+ }
+ mTouchSession.pop();
+ break;
+ default:
+ mVelocityTracker.addMovement(motionEvent);
+ break;
+ }
+ }
+
+ private ValueAnimator createExpansionAnimator(float targetExpansion) {
+ final ValueAnimator animator =
+ mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion);
+ animator.addUpdateListener(
+ animation -> {
+ setPanelExpansion((float) animation.getAnimatedValue());
+ });
+ return animator;
+ }
+
+ protected boolean flingRevealsOverlay(float velocity, float velocityVector) {
+ // Fully expand if the user has expanded the bouncer less than halfway or final velocity was
+ // positive, indicating an downward direction.
+ if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+ return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD;
+ } else {
+ return velocity > 0;
+ }
+ }
+
+ protected void flingToExpansion(float velocity, float expansion) {
+ final float viewHeight = mStatusBar.getDisplayHeight();
+ final float currentHeight = viewHeight * mCurrentExpansion;
+ final float targetHeight = viewHeight * expansion;
+
+ final ValueAnimator animator = createExpansionAnimator(expansion);
+ if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) {
+ // The animation utils deal in pixel units, rather than expansion height.
+ mFlingAnimationUtils.apply(animator, currentHeight, targetHeight, velocity, viewHeight);
+ } else {
+ mFlingAnimationUtilsClosing.apply(
+ animator, mCurrentExpansion, currentHeight, targetHeight, viewHeight);
+ }
+
+ animator.start();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
new file mode 100644
index 0000000..b9436f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
@@ -0,0 +1,128 @@
+/*
+ * 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.dreams.touch.dagger;
+
+import android.animation.ValueAnimator;
+import android.content.res.Resources;
+import android.util.TypedValue;
+import android.view.VelocityTracker;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
+import com.android.systemui.dreams.touch.DreamTouchHandler;
+import com.android.systemui.statusbar.phone.PanelViewController;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import javax.inject.Named;
+import javax.inject.Provider;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoSet;
+
+/**
+ * This module captures the components associated with {@link BouncerSwipeTouchHandler}.
+ */
+@Module
+public class BouncerSwipeModule {
+ /**
+ * The region, defined as the percentage of the screen, from which a touch gesture to start
+ * swiping up to the bouncer can occur.
+ */
+ public static final String SWIPE_TO_BOUNCER_START_REGION = "swipe_to_bouncer_start_region";
+
+ /**
+ * The {@link android.view.animation.AnimationUtils} for animating the bouncer closing.
+ */
+ public static final String SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING =
+ "swipe_to_bouncer_fling_animation_utils_closing";
+
+ /**
+ * The {@link android.view.animation.AnimationUtils} for animating the bouncer opening.
+ */
+ public static final String SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING =
+ "swipe_to_bouncer_fling_animation_utils_opening";
+
+ /**
+ * Provides {@link BouncerSwipeTouchHandler} for inclusion in touch handling over the dream.
+ */
+ @Provides
+ @IntoSet
+ public static DreamTouchHandler providesBouncerSwipeTouchHandler(
+ BouncerSwipeTouchHandler touchHandler) {
+ return touchHandler;
+ }
+
+ /**
+ * Provides {@link android.view.animation.AnimationUtils} for closing.
+ */
+ @Provides
+ @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+ public static FlingAnimationUtils providesSwipeToBouncerFlingAnimationUtilsClosing(
+ Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) {
+ return flingAnimationUtilsBuilderProvider.get()
+ .reset()
+ .setMaxLengthSeconds(PanelViewController.FLING_CLOSING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR)
+ .build();
+ }
+
+ /**
+ * Provides {@link android.view.animation.AnimationUtils} for opening.
+ */
+ @Provides
+ @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+ public static FlingAnimationUtils providesSwipeToBouncerFlingAnimationUtilsOpening(
+ Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) {
+ return flingAnimationUtilsBuilderProvider.get()
+ .reset()
+ .setMaxLengthSeconds(PanelViewController.FLING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR)
+ .build();
+ }
+
+ /**
+ * Provides the region to start swipe gestures from.
+ */
+ @Provides
+ @Named(SWIPE_TO_BOUNCER_START_REGION)
+ public static float providesSwipeToBouncerStartRegion(@Main Resources resources) {
+ TypedValue typedValue = new TypedValue();
+ resources.getValue(R.dimen.dream_overlay_bouncer_start_region_screen_percentage,
+ typedValue, true);
+ return typedValue.getFloat();
+ }
+
+ /**
+ * Provides the default {@link BouncerSwipeTouchHandler.ValueAnimatorCreator}, which is simply
+ * a wrapper around {@link ValueAnimator}.
+ */
+ @Provides
+ public static BouncerSwipeTouchHandler.ValueAnimatorCreator providesValueAnimatorCreator() {
+ return (start, finish) -> ValueAnimator.ofFloat(start, finish);
+ }
+
+ /**
+ * Provides the default {@link BouncerSwipeTouchHandler.VelocityTrackerFactory}. which is a
+ * passthrough to {@link android.view.VelocityTracker}.
+ */
+ @Provides
+ public static BouncerSwipeTouchHandler.VelocityTrackerFactory providesVelocityTrackerFactory() {
+ return () -> VelocityTracker.obtain();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
index 7b77b59..dad0004 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
@@ -21,8 +21,10 @@
/**
* {@link DreamTouchModule} encapsulates dream touch-related components.
*/
-@Module(subcomponents = {
- InputSessionComponent.class,
+@Module(includes = {
+ BouncerSwipeModule.class,
+ }, subcomponents = {
+ InputSessionComponent.class,
})
public interface DreamTouchModule {
String INPUT_SESSION_NAME = "INPUT_SESSION_NAME";
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 97edf3b..357a68f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -107,11 +107,16 @@
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 =
new BooleanFlag(601, false);
+ public static final BooleanFlag STATUS_BAR_USER_SWITCHER =
+ new BooleanFlag(602, false);
+
/***************************************/
// 700 - dialer/calls
public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP =
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
index d1a103e..de67ba8 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 @@
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 @@
// 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 @@
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/hdmi/HdmiCecSetMenuLanguageActivity.java b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java
new file mode 100644
index 0000000..b304c3c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java
@@ -0,0 +1,98 @@
+/*
+ * 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.hdmi;
+
+import android.hardware.hdmi.HdmiControlManager;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.tv.TvBottomSheetActivity;
+
+import javax.inject.Inject;
+
+/**
+ * Confirmation dialog shown when Set Menu Language CEC message was received.
+ */
+public class HdmiCecSetMenuLanguageActivity extends TvBottomSheetActivity
+ implements View.OnClickListener {
+ private static final String TAG = HdmiCecSetMenuLanguageActivity.class.getSimpleName();
+
+ private final HdmiCecSetMenuLanguageHelper mHdmiCecSetMenuLanguageHelper;
+
+ @Inject
+ public HdmiCecSetMenuLanguageActivity(
+ HdmiCecSetMenuLanguageHelper hdmiCecSetMenuLanguageHelper) {
+ mHdmiCecSetMenuLanguageHelper = hdmiCecSetMenuLanguageHelper;
+ }
+
+ @Override
+ public final void onCreate(Bundle b) {
+ super.onCreate(b);
+ getWindow().addPrivateFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ String languageTag = getIntent().getStringExtra(HdmiControlManager.EXTRA_LOCALE);
+ mHdmiCecSetMenuLanguageHelper.setLocale(languageTag);
+ if (mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()) {
+ finish();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ CharSequence title = getString(R.string.hdmi_cec_set_menu_language_title,
+ mHdmiCecSetMenuLanguageHelper.getLocale().getDisplayLanguage());
+ CharSequence text = getString(R.string.hdmi_cec_set_menu_language_description);
+ initUI(title, text);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.bottom_sheet_positive_button) {
+ mHdmiCecSetMenuLanguageHelper.acceptLocale();
+ } else {
+ mHdmiCecSetMenuLanguageHelper.declineLocale();
+ }
+ finish();
+ }
+
+ void initUI(CharSequence title, CharSequence text) {
+ TextView titleTextView = findViewById(R.id.bottom_sheet_title);
+ TextView contentTextView = findViewById(R.id.bottom_sheet_body);
+ ImageView icon = findViewById(R.id.bottom_sheet_icon);
+ ImageView secondIcon = findViewById(R.id.bottom_sheet_second_icon);
+ Button okButton = findViewById(R.id.bottom_sheet_positive_button);
+ Button cancelButton = findViewById(R.id.bottom_sheet_negative_button);
+
+ titleTextView.setText(title);
+ contentTextView.setText(text);
+ icon.setImageResource(com.android.internal.R.drawable.ic_settings_language);
+ secondIcon.setVisibility(View.GONE);
+
+ okButton.setText(R.string.hdmi_cec_set_menu_language_accept);
+ okButton.setOnClickListener(this);
+
+ cancelButton.setText(R.string.hdmi_cec_set_menu_language_decline);
+ cancelButton.setOnClickListener(this);
+ cancelButton.requestFocus();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java
new file mode 100644
index 0000000..1f58112
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java
@@ -0,0 +1,97 @@
+/*
+ * 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.hdmi;
+
+import android.provider.Settings;
+
+import com.android.internal.app.LocalePicker;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * Helper class to separate model and view for system language change initiated by HDMI CEC.
+ */
+@SysUISingleton
+public class HdmiCecSetMenuLanguageHelper {
+ private static final String TAG = HdmiCecSetMenuLanguageHelper.class.getSimpleName();
+ private static final String SEPARATOR = ",";
+
+ private final Executor mBackgroundExecutor;
+ private final SecureSettings mSecureSettings;
+
+ private Locale mLocale;
+ private HashSet<String> mDenylist;
+
+ @Inject
+ public HdmiCecSetMenuLanguageHelper(@Background Executor executor,
+ SecureSettings secureSettings) {
+ mBackgroundExecutor = executor;
+ mSecureSettings = secureSettings;
+ String denylist = mSecureSettings.getString(
+ Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST);
+ mDenylist = new HashSet<>(denylist == null
+ ? Collections.EMPTY_SET
+ : Arrays.asList(denylist.split(SEPARATOR)));
+ }
+
+ /**
+ * Set internal locale based on given language tag.
+ */
+ public void setLocale(String languageTag) {
+ mLocale = Locale.forLanguageTag(languageTag);
+ }
+
+ /**
+ * Returns the locale from {@code <Set Menu Language>} CEC message.
+ */
+ public Locale getLocale() {
+ return mLocale;
+ }
+
+ /**
+ * Returns whether the locale from {@code <Set Menu Language>} CEC message was already
+ * denylisted.
+ */
+ public boolean isLocaleDenylisted() {
+ return mDenylist.contains(mLocale.toLanguageTag());
+ }
+
+ /**
+ * Accepts the new locale and updates system language.
+ */
+ public void acceptLocale() {
+ mBackgroundExecutor.execute(() -> LocalePicker.updateLocale(mLocale));
+ }
+
+ /**
+ * Declines the locale and puts it on the denylist.
+ */
+ public void declineLocale() {
+ mDenylist.add(mLocale.toLanguageTag());
+ mSecureSettings.putString(Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST,
+ String.join(SEPARATOR, mDenylist));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 701d139..fd2c6dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -106,6 +106,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -138,7 +139,7 @@
* state of the keyguard, power management events that effect whether the keyguard
* should be shown or reset, callbacks to the phone window manager to notify
* it of when the keyguard is showing, and events from the keyguard view itself
- * stating that the keyguard was succesfully unlocked.
+ * stating that the keyguard was successfully unlocked.
*
* Note that the keyguard view is shown when the screen is off (as appropriate)
* so that once the screen comes on, it will be ready immediately.
@@ -152,15 +153,15 @@
* - the keyguard is showing
*
* Example external events that translate to keyguard view changes:
- * - screen turned off -> reset the keyguard, and show it so it will be ready
+ * - screen turned off -> reset the keyguard, and show it, so it will be ready
* next time the screen turns on
* - keyboard is slid open -> if the keyguard is not secure, hide it
*
* Events from the keyguard view:
- * - user succesfully unlocked keyguard -> hide keyguard view, and no longer
+ * - user successfully unlocked keyguard -> hide keyguard view, and no longer
* restrict input events.
*
- * Note: in addition to normal power managment events that effect the state of
+ * Note: in addition to normal power management events that effect the state of
* whether the keyguard should be showing, external apps and services may request
* that the keyguard be disabled via {@link #setKeyguardEnabled(boolean)}. When
* false, this will override all other conditions for turning on the keyguard.
@@ -224,7 +225,7 @@
/**
* How long we'll wait for the {@link ViewMediatorCallback#keyguardDoneDrawing()}
* callback before unblocking a call to {@link #setKeyguardEnabled(boolean)}
- * that is reenabling the keyguard.
+ * that is re-enabling the keyguard.
*/
private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000;
@@ -233,6 +234,7 @@
* keyguard to show even if it is disabled for the current user.
*/
public static final String OPTION_FORCE_SHOW = "force_show";
+ private final DreamOverlayStateController mDreamOverlayStateController;
/** The stream type that the lock sounds are tied to. */
private int mUiSoundsStreamType;
@@ -273,14 +275,14 @@
// these are protected by synchronized (this)
/**
- * External apps (like the phone app) can tell us to disable the keygaurd.
+ * External apps (like the phone app) can tell us to disable the keyguard.
*/
private boolean mExternallyEnabled = true;
/**
* Remember if an external call to {@link #setKeyguardEnabled} with value
* false caused us to hide the keyguard, so that we need to reshow it once
- * the keygaurd is reenabled with another call with value true.
+ * the keyguard is re-enabled with another call with value true.
*/
private boolean mNeedToReshowWhenReenabled = false;
@@ -291,6 +293,9 @@
// AOD is enabled and status bar is in AOD state.
private boolean mAodShowing;
+ // Dream overlay is visible.
+ private boolean mDreamOverlayShowing;
+
/** Cached value of #isInputRestricted */
private boolean mInputRestricted;
@@ -304,7 +309,7 @@
private int mDelayedShowingSequence;
/**
- * Simiar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
+ * Similar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
*/
private int mDelayedProfileShowingSequence;
@@ -341,7 +346,7 @@
private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
/**
- * Whether a hide is pending an we are just waiting for #startKeyguardExitAnimation to be
+ * Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
* called.
* */
private boolean mHiding;
@@ -355,7 +360,7 @@
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
/**
- * {@link #setKeyguardEnabled} waits on this condition when it reenables
+ * {@link #setKeyguardEnabled} waits on this condition when it re-enables
* the keyguard.
*/
private boolean mWaitingUntilKeyguardVisible = false;
@@ -470,6 +475,14 @@
}
};
+ private final DreamOverlayStateController.Callback mDreamOverlayStateCallback =
+ new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ mDreamOverlayShowing = mDreamOverlayStateController.isOverlayActive();
+ }
+ };
+
KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@Override
@@ -494,7 +507,7 @@
synchronized (KeyguardViewMediator.this) {
resetKeyguardDonePendingLocked();
if (mLockPatternUtils.isLockScreenDisabled(userId)) {
- // If we switching to a user that has keyguard disabled, dismiss keyguard.
+ // If we are switching to a user that has keyguard disabled, dismiss keyguard.
dismiss(null /* callback */, null /* message */);
} else {
resetStateLocked();
@@ -508,7 +521,7 @@
if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
if (userId != UserHandle.USER_SYSTEM) {
UserInfo info = UserManager.get(mContext).getUserInfo(userId);
- // Don't try to dismiss if the user has Pin/Patter/Password set
+ // Don't try to dismiss if the user has Pin/Pattern/Password set
if (info == null || mLockPatternUtils.isSecure(userId)) {
return;
} else if (info.isGuest() || info.isDemo()) {
@@ -836,6 +849,7 @@
Lazy<NotificationShadeDepthController> notificationShadeDepthController,
ScreenOnCoordinator screenOnCoordinator,
InteractionJankMonitor interactionJankMonitor,
+ DreamOverlayStateController dreamOverlayStateController,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy) {
super(context);
mFalsingCollector = falsingCollector;
@@ -875,6 +889,7 @@
mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy;
mScreenOffAnimationController = screenOffAnimationController;
mInteractionJankMonitor = interactionJankMonitor;
+ mDreamOverlayStateController = dreamOverlayStateController;
}
public void userActivity() {
@@ -980,6 +995,7 @@
mSystemReady = true;
doKeyguardLocked(null);
mUpdateMonitor.registerCallback(mUpdateCallback);
+ mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
}
// Most services aren't available until the system reaches the ready state, so we
// send it here when the device first boots.
@@ -1041,7 +1057,7 @@
mUpdateMonitor.dispatchStartedGoingToSleep(offReason);
- // Reset keyguard going away state so we can start listening for fingerprint. We
+ // Reset keyguard going away state, so we can start listening for fingerprint. We
// explicitly DO NOT want to call
// mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false)
// here, since that will mess with the device lock state.
@@ -1121,9 +1137,9 @@
}
private long getLockTimeout(int userId) {
- // if the screen turned off because of timeout or the user hit the power button
+ // if the screen turned off because of timeout or the user hit the power button,
// and we don't need to lock immediately, set an alarm
- // to enable it a little bit later (i.e, give the user a chance
+ // to enable it a bit later (i.e, give the user a chance
// to turn the screen back on within a certain window without
// having to unlock the screen)
final ContentResolver cr = mContext.getContentResolver();
@@ -1217,7 +1233,7 @@
}
/**
- * Let's us know when the device is waking up.
+ * It will let us know when the device is waking up.
*/
public void onStartedWakingUp(boolean cameraGestureTriggered) {
Trace.beginSection("KeyguardViewMediator#onStartedWakingUp");
@@ -1299,7 +1315,7 @@
if (mExitSecureCallback != null) {
if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring");
// we're in the process of handling a request to verify the user
- // can get past the keyguard. ignore extraneous requests to disable / reenable
+ // can get past the keyguard. ignore extraneous requests to disable / re-enable
return;
}
@@ -1310,7 +1326,7 @@
updateInputRestrictedLocked();
hideLocked();
} else if (enabled && mNeedToReshowWhenReenabled) {
- // reenabled after previously hidden, reshow
+ // re-enabled after previously hidden, reshow
if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling "
+ "status bar expansion");
mNeedToReshowWhenReenabled = false;
@@ -1328,8 +1344,8 @@
} else {
showLocked(null);
- // block until we know the keygaurd is done drawing (and post a message
- // to unblock us after a timeout so we don't risk blocking too long
+ // block until we know the keyguard is done drawing (and post a message
+ // to unblock us after a timeout, so we don't risk blocking too long
// and causing an ANR).
mWaitingUntilKeyguardVisible = true;
mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
@@ -1917,7 +1933,7 @@
mExitSecureCallback = null;
- // after succesfully exiting securely, no need to reshow
+ // after successfully exiting securely, no need to reshow
// the keyguard when they've released the lock
mExternallyEnabled = true;
mNeedToReshowWhenReenabled = false;
@@ -1992,7 +2008,7 @@
if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
int id = mLockSounds.play(soundId,
- mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
+ mLockSoundVolume, mLockSoundVolume, 1/*priority*/, 0/*loop*/, 1.0f/*rate*/);
synchronized (this) {
mLockSoundStreamId = id;
}
@@ -2078,7 +2094,7 @@
|| mScreenOnCoordinator.getWakeAndUnlocking()
&& mWallpaperSupportsAmbientMode) {
// When the wallpaper supports ambient mode, the scrim isn't fully opaque during
- // wake and unlock and we should fade in the app on top of the wallpaper
+ // wake and unlock, and we should fade in the app on top of the wallpaper
flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
}
if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) {
@@ -2123,7 +2139,7 @@
Trace.beginSection("KeyguardViewMediator#handleHide");
// It's possible that the device was unlocked in a dream state. It's time to wake up.
- if (mAodShowing) {
+ if (mAodShowing || mDreamOverlayShowing) {
PowerManager pm = mContext.getSystemService(PowerManager.class);
pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
"com.android.systemui:BOUNCER_DOZING");
@@ -2167,15 +2183,15 @@
synchronized (KeyguardViewMediator.this) {
// Tell ActivityManager that we canceled the keyguard animation if
- // handleStartKeyguardExitAnimation was called but we're not hiding the keyguard, unless
- // we're animating the surface behind the keyguard and will be hiding the keyguard
- // shortly.
+ // handleStartKeyguardExitAnimation was called, but we're not hiding the keyguard,
+ // unless we're animating the surface behind the keyguard and will be hiding the
+ // keyguard shortly.
if (!mHiding
&& !mSurfaceBehindRemoteAnimationRequested
&& !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
if (finishedCallback != null) {
// There will not execute animation, send a finish callback to ensure the remote
- // animation won't hanging there.
+ // animation won't hang there.
try {
finishedCallback.onAnimationFinished();
} catch (RemoteException e) {
@@ -2192,7 +2208,7 @@
if (mScreenOnCoordinator.getWakeAndUnlocking()) {
// Hack level over 9000: To speed up wake-and-unlock sequence, force it to report
- // the next draw from here so we don't have to wait for window manager to signal
+ // the next draw from here, so we don't have to wait for window manager to signal
// this to our ViewRootImpl.
mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw();
mScreenOnCoordinator.setWakeAndUnlocking(false);
@@ -2344,7 +2360,7 @@
}
/**
- * Called if the keyguard exit animation has been cancelled and we should dismiss to the
+ * Called if the keyguard exit animation has been cancelled, and we should dismiss to the
* keyguard.
*
* This can happen due to the system cancelling the RemoteAnimation (due to a timeout, a new
@@ -2595,7 +2611,7 @@
}
/**
- * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+ * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
* the wallpaper and keyguard flag, and WindowManager has started running keyguard exit
* animation.
*
@@ -2609,7 +2625,7 @@
}
/**
- * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+ * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
* the wallpaper and keyguard flag, and System UI should start running keyguard exit animation.
*
* @param apps The list of apps to animate.
@@ -2625,7 +2641,7 @@
}
/**
- * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+ * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
* the wallpaper and keyguard flag, and start running keyguard exit animation.
*
* @param startTime the start time of the animation in uptime milliseconds. Deprecated.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index f14d130..b49b49cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -37,6 +37,7 @@
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -100,6 +101,7 @@
Lazy<NotificationShadeDepthController> notificationShadeDepthController,
ScreenOnCoordinator screenOnCoordinator,
InteractionJankMonitor interactionJankMonitor,
+ DreamOverlayStateController dreamOverlayStateController,
Lazy<NotificationShadeWindowController> notificationShadeWindowController) {
return new KeyguardViewMediator(
context,
@@ -125,6 +127,7 @@
notificationShadeDepthController,
screenOnCoordinator,
interactionJankMonitor,
+ dreamOverlayStateController,
notificationShadeWindowController
);
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index b15807c..6d589aa 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 @@
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 @@
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/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
index 44727f2..48f4826 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -159,8 +159,7 @@
}
fun refreshMediaPosition() {
- val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD ||
- statusBarStateController.state == StatusBarState.FULLSCREEN_USER_SWITCHER)
+ val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD)
// mediaHost.visible required for proper animations handling
visible = mediaHost.visible &&
!bypassController.bypassEnabled &&
@@ -196,4 +195,4 @@
visibilityChangedListener?.invoke(newVisibility == View.VISIBLE)
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index c404f7a..f893f36 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -411,7 +411,6 @@
// Returns true if new player is added
private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData): Boolean {
- val dataCopy = data.copy(backgroundColor = bgColor)
MediaPlayerData.moveIfExists(oldKey, key)
val existingPlayer = MediaPlayerData.getMediaPlayer(key)
val curVisibleMediaKey = MediaPlayerData.playerKeys()
@@ -431,14 +430,14 @@
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
- newPlayer.bindPlayer(dataCopy, key)
+ newPlayer.bindPlayer(data, key)
newPlayer.setListening(currentlyExpanded)
- MediaPlayerData.addMediaPlayer(key, dataCopy, newPlayer, systemClock)
+ MediaPlayerData.addMediaPlayer(key, data, newPlayer, systemClock)
updatePlayerToState(newPlayer, noAnimation = true)
reorderAllPlayers(curVisibleMediaKey)
} else {
- existingPlayer.bindPlayer(dataCopy, key)
- MediaPlayerData.addMediaPlayer(key, dataCopy, existingPlayer, systemClock)
+ existingPlayer.bindPlayer(data, key)
+ MediaPlayerData.addMediaPlayer(key, data, existingPlayer, systemClock)
if (visualStabilityManager.isReorderingAllowed || shouldScrollToActivePlayer) {
reorderAllPlayers(curVisibleMediaKey)
} else {
@@ -543,7 +542,11 @@
}
private fun getForegroundColor(): Int {
- return context.getColor(android.R.color.system_accent2_900)
+ return if (mediaFlags.useMediaSessionLayout()) {
+ context.getColor(android.R.color.system_neutral2_200)
+ } else {
+ context.getColor(android.R.color.system_accent2_900)
+ }
}
private fun updatePageIndicator() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 69a7ec3..b3e6682 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
import android.app.PendingIntent;
+import android.app.WallpaperColors;
import android.app.smartspace.SmartspaceAction;
import android.content.Context;
import android.content.Intent;
@@ -40,6 +41,7 @@
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
+import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -54,6 +56,7 @@
import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.monet.ColorScheme;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
@@ -405,7 +408,7 @@
seamlessView.setContentDescription(deviceString);
// Dismiss
- mMediaViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
+ mMediaViewHolder.getDismissText().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
mMediaViewHolder.getDismiss().setEnabled(isDismissible);
mMediaViewHolder.getDismiss().setOnClickListener(v -> {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
@@ -438,11 +441,10 @@
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
// Album art
- PlayerViewHolder playerHolder = (PlayerViewHolder) mMediaViewHolder;
- ImageView albumView = playerHolder.getAlbumView();
+ ImageView albumView = mMediaViewHolder.getAlbumView();
boolean hasArtwork = data.getArtwork() != null;
if (hasArtwork) {
- Drawable artwork = scaleDrawable(data.getArtwork());
+ Drawable artwork = getScaledThumbnail(data.getArtwork());
albumView.setPadding(0, 0, 0, 0);
albumView.setImageDrawable(artwork);
} else {
@@ -548,6 +550,19 @@
/** Bind elements specific to PlayerSessionViewHolder */
private void bindSessionPlayer(@NonNull MediaData data, String key) {
+ // Default colors
+ int surfaceColor = mBackgroundColor;
+ int accentPrimary = com.android.settingslib.Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.textColorPrimary).getDefaultColor();
+ int textPrimary = com.android.settingslib.Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.textColorPrimary).getDefaultColor();
+ int textPrimaryInverse = com.android.settingslib.Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.textColorPrimaryInverse).getDefaultColor();
+ int textSecondary = com.android.settingslib.Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.textColorSecondary).getDefaultColor();
+ int textTertiary = com.android.settingslib.Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.textColorTertiary).getDefaultColor();
+
// App icon - use launcher icon
ImageView appIconView = mMediaViewHolder.getAppIcon();
appIconView.clearColorFilter();
@@ -567,26 +582,106 @@
appIconView.setColorFilter(color);
}
+ // Album art
+ ColorScheme colorScheme = null;
+ ImageView albumView = mMediaViewHolder.getAlbumView();
+ boolean hasArtwork = data.getArtwork() != null;
+ if (hasArtwork) {
+ colorScheme = new ColorScheme(WallpaperColors.fromBitmap(data.getArtwork().getBitmap()),
+ true);
+
+ // Scale artwork to fit background
+ int width = mMediaViewHolder.getPlayer().getWidth();
+ int height = mMediaViewHolder.getPlayer().getHeight();
+ Drawable artwork = getScaledBackground(data.getArtwork(), width, height);
+ albumView.setPadding(0, 0, 0, 0);
+ albumView.setImageDrawable(artwork);
+ albumView.setClipToOutline(true);
+ } else {
+ // If there's no artwork, use colors from the app icon
+ try {
+ Drawable icon = mContext.getPackageManager().getApplicationIcon(
+ data.getPackageName());
+ colorScheme = new ColorScheme(WallpaperColors.fromDrawable(icon), true);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Cannot find icon for package " + data.getPackageName(), e);
+ }
+ }
+
+ // Get colors for player
+ if (colorScheme != null) {
+ surfaceColor = colorScheme.getAccent2().get(9); // A2-800
+ accentPrimary = colorScheme.getAccent1().get(2); // A1-100
+ textPrimary = colorScheme.getNeutral1().get(1); // N1-50
+ textPrimaryInverse = colorScheme.getNeutral1().get(10); // N1-900
+ textSecondary = colorScheme.getNeutral2().get(3); // N2-200
+ textTertiary = colorScheme.getNeutral2().get(5); // N2-400
+ }
+
+ ColorStateList bgColorList = ColorStateList.valueOf(surfaceColor);
+ ColorStateList accentColorList = ColorStateList.valueOf(accentPrimary);
+ ColorStateList textColorList = ColorStateList.valueOf(textPrimary);
+
+ // Gradient and background (visible when there is no art)
+ albumView.setForegroundTintList(ColorStateList.valueOf(surfaceColor));
+ albumView.setBackgroundTintList(
+ ColorStateList.valueOf(surfaceColor));
+ mMediaViewHolder.getPlayer().setBackgroundTintList(bgColorList);
+
+ // Metadata text
+ mMediaViewHolder.getTitleText().setTextColor(textPrimary);
+ mMediaViewHolder.getArtistText().setTextColor(textSecondary);
+
+ // Seekbar
+ SeekBar seekbar = mMediaViewHolder.getSeekBar();
+ seekbar.getThumb().setTintList(textColorList);
+ seekbar.setProgressTintList(textColorList);
+ seekbar.setProgressBackgroundTintList(ColorStateList.valueOf(textTertiary));
+
+ // Output switcher
+ View seamlessView = mMediaViewHolder.getSeamlessButton();
+ seamlessView.setBackgroundTintList(accentColorList);
+ ImageView seamlessIconView = mMediaViewHolder.getSeamlessIcon();
+ seamlessIconView.setImageTintList(bgColorList);
+ TextView seamlessText = mMediaViewHolder.getSeamlessText();
+ seamlessText.setTextColor(surfaceColor);
+
// Media action buttons
MediaButton semanticActions = data.getSemanticActions();
if (semanticActions != null) {
PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder;
- setSemanticButton(sessionHolder.getActionPlayPause(),
- semanticActions.getPlayOrPause());
- setSemanticButton(sessionHolder.getActionNext(),
- semanticActions.getNextOrCustom());
- setSemanticButton(sessionHolder.getActionPrev(),
- semanticActions.getPrevOrCustom());
- setSemanticButton(sessionHolder.getActionStart(),
- semanticActions.getStartCustom());
- setSemanticButton(sessionHolder.getActionEnd(),
- semanticActions.getEndCustom());
+
+ // Play/pause button has a background
+ sessionHolder.getActionPlayPause().setBackgroundTintList(accentColorList);
+ setSemanticButton(sessionHolder.getActionPlayPause(), semanticActions.getPlayOrPause(),
+ ColorStateList.valueOf(textPrimaryInverse));
+
+ setSemanticButton(sessionHolder.getActionNext(), semanticActions.getNextOrCustom(),
+ textColorList);
+ setSemanticButton(sessionHolder.getActionPrev(), semanticActions.getPrevOrCustom(),
+ textColorList);
+ setSemanticButton(sessionHolder.getActionStart(), semanticActions.getStartCustom(),
+ textColorList);
+ setSemanticButton(sessionHolder.getActionEnd(), semanticActions.getEndCustom(),
+ textColorList);
} else {
Log.w(TAG, "Using semantic player, but did not get buttons");
}
+
+ // Long press buttons
+ mMediaViewHolder.getLongPressText().setTextColor(textColorList);
+ mMediaViewHolder.getSettingsText().setTextColor(textColorList);
+ mMediaViewHolder.getSettingsText().setBackgroundTintList(accentColorList);
+ mMediaViewHolder.getCancelText().setTextColor(textColorList);
+ mMediaViewHolder.getCancelText().setBackgroundTintList(accentColorList);
+ mMediaViewHolder.getDismissText().setTextColor(textColorList);
+ mMediaViewHolder.getDismissText().setBackgroundTintList(accentColorList);
+
}
- private void setSemanticButton(final ImageButton button, MediaAction mediaAction) {
+ private void setSemanticButton(final ImageButton button, MediaAction mediaAction,
+ ColorStateList fgColor) {
+ button.setImageTintList(fgColor);
if (mediaAction != null) {
button.setImageIcon(mediaAction.getIcon());
button.setContentDescription(mediaAction.getContentDescription());
@@ -844,8 +939,11 @@
mMediaViewController.openGuts();
}
+ /**
+ * Scale drawable to fit into the square album art thumbnail
+ */
@UiThread
- private Drawable scaleDrawable(Icon icon) {
+ private Drawable getScaledThumbnail(Icon icon) {
if (icon == null) {
return null;
}
@@ -870,6 +968,25 @@
}
/**
+ * Scale artwork to fill the background of the panel
+ */
+ @UiThread
+ private Drawable getScaledBackground(Icon icon, int width, int height) {
+ if (icon == null) {
+ return null;
+ }
+ Drawable drawable = icon.loadDrawable(mContext);
+ Rect bounds = new Rect(0, 0, width, height);
+ if (bounds.width() > width || bounds.height() > height) {
+ float offsetX = (bounds.width() - width) / 2.0f;
+ float offsetY = (bounds.height() - height) / 2.0f;
+ bounds.offset((int) -offsetX, (int) -offsetY);
+ }
+ drawable.setBounds(bounds);
+ return drawable;
+ }
+
+ /**
* Get the current media controller
*
* @return the controller
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index d926e7d..0223c60 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -30,9 +30,7 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
-import android.graphics.Canvas
import android.graphics.ImageDecoder
-import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.MediaDescription
import android.media.MediaMetadata
@@ -562,7 +560,7 @@
val mediaController = mediaControllerFactory.create(token)
val metadata = mediaController.metadata
- // Foreground and Background colors computed from album art
+ // Album art
val notif: Notification = sbn.notification
var artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART)
if (artworkBitmap == null) {
@@ -576,24 +574,6 @@
} else {
Icon.createWithBitmap(artworkBitmap)
}
- if (artWorkIcon != null) {
- // If we have art, get colors from that
- if (artworkBitmap == null) {
- if (artWorkIcon.type == Icon.TYPE_BITMAP ||
- artWorkIcon.type == Icon.TYPE_ADAPTIVE_BITMAP) {
- artworkBitmap = artWorkIcon.bitmap
- } else {
- val drawable: Drawable = artWorkIcon.loadDrawable(context)
- artworkBitmap = Bitmap.createBitmap(
- drawable.intrinsicWidth,
- drawable.intrinsicHeight,
- Bitmap.Config.ARGB_8888)
- val canvas = Canvas(artworkBitmap)
- drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
- drawable.draw(canvas)
- }
- }
- }
// App name
val builder = Notification.Builder.recoverBuilder(context, notif)
@@ -787,30 +767,28 @@
return when (action) {
PlaybackState.ACTION_PLAY -> {
MediaAction(
- Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_play),
+ Icon.createWithResource(context, R.drawable.ic_media_play),
{ controller.transportControls.play() },
context.getString(R.string.controls_media_button_play)
)
}
PlaybackState.ACTION_PAUSE -> {
MediaAction(
- Icon.createWithResource(context,
- com.android.internal.R.drawable.ic_media_pause),
+ Icon.createWithResource(context, R.drawable.ic_media_pause),
{ controller.transportControls.pause() },
context.getString(R.string.controls_media_button_pause)
)
}
PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
MediaAction(
- Icon.createWithResource(context,
- com.android.internal.R.drawable.ic_media_previous),
+ Icon.createWithResource(context, R.drawable.ic_media_prev),
{ controller.transportControls.skipToPrevious() },
context.getString(R.string.controls_media_button_prev)
)
}
PlaybackState.ACTION_SKIP_TO_NEXT -> {
MediaAction(
- Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_next),
+ Icon.createWithResource(context, R.drawable.ic_media_next),
{ controller.transportControls.skipToNext() },
context.getString(R.string.controls_media_button_next)
)
@@ -900,7 +878,7 @@
private fun getResumeMediaAction(action: Runnable): MediaAction {
return MediaAction(
- Icon.createWithResource(context, R.drawable.lb_ic_play).setTint(themeText),
+ Icon.createWithResource(context, R.drawable.ic_media_play).setTint(themeText),
action,
context.getString(R.string.controls_media_resume)
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index c8cd432..6145f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -31,6 +31,7 @@
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.CrossFadeHelper
@@ -82,7 +83,8 @@
private val notifLockscreenUserManager: NotificationLockscreenUserManager,
configurationController: ConfigurationController,
wakefulnessLifecycle: WakefulnessLifecycle,
- private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val dreamOverlayStateController: DreamOverlayStateController
) {
/**
@@ -167,7 +169,7 @@
})
}
- private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_LOCKSCREEN + 1)
+ private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1)
/**
* The last location where this view was at before going to the desired location. This is
* useful for guided transitions.
@@ -349,6 +351,17 @@
}
/**
+ * Is the doze animation currently Running
+ */
+ private var dreamOverlayActive: Boolean = false
+ private set(value) {
+ if (field != value) {
+ field = value
+ updateDesiredLocation(forceNoAnimation = true)
+ }
+ }
+
+ /**
* The current cross fade progress. 0.5f means it's just switching
* between the start and the end location and the content is fully faded, while 0.75f means
* that we're halfway faded in again in the target state.
@@ -444,6 +457,12 @@
}
})
+ dreamOverlayStateController.addCallback(object : DreamOverlayStateController.Callback {
+ override fun onStateChanged() {
+ dreamOverlayStateController.isOverlayActive.also { dreamOverlayActive = it }
+ }
+ })
+
wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer {
override fun onFinishedGoingToSleep() {
goingToSleep = false
@@ -548,8 +567,7 @@
previousLocation = this.desiredLocation
} else if (forceStateUpdate) {
val onLockscreen = (!bypassController.bypassEnabled &&
- (statusbarState == StatusBarState.KEYGUARD ||
- statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
+ (statusbarState == StatusBarState.KEYGUARD))
if (desiredLocation == LOCATION_QS && previousLocation == LOCATION_LOCKSCREEN &&
!onLockscreen) {
// If media active state changed and the device is now unlocked, update the
@@ -936,10 +954,10 @@
return desiredLocation
}
val onLockscreen = (!bypassController.bypassEnabled &&
- (statusbarState == StatusBarState.KEYGUARD ||
- statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
+ (statusbarState == StatusBarState.KEYGUARD))
val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
val location = when {
+ dreamOverlayActive -> LOCATION_DREAM_OVERLAY
(qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
!hasActiveMedia -> LOCATION_QS
@@ -1035,6 +1053,11 @@
const val LOCATION_LOCKSCREEN = 2
/**
+ * Attached on the dream overlay
+ */
+ const val LOCATION_DREAM_OVERLAY = 3
+
+ /**
* Attached at the root of the hierarchy in an overlay
*/
const val IN_OVERLAY = -1000
@@ -1062,4 +1085,4 @@
@IntDef(prefix = ["LOCATION_"], value = [MediaHierarchyManager.LOCATION_QS,
MediaHierarchyManager.LOCATION_QQS, MediaHierarchyManager.LOCATION_LOCKSCREEN])
@Retention(AnnotationRetention.SOURCE)
-annotation class MediaLocation
\ No newline at end of file
+annotation class MediaLocation
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
index c333b50..e57b247 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
@@ -35,6 +35,7 @@
val player = itemView as TransitionLayout
// Player information
+ val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
val titleText = itemView.requireViewById<TextView>(R.id.header_title)
val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
@@ -53,8 +54,9 @@
// Settings screen
val longPressText = itemView.requireViewById<TextView>(R.id.remove_text)
val cancel = itemView.requireViewById<View>(R.id.cancel)
+ val cancelText = itemView.requireViewById<TextView>(R.id.cancel_text)
val dismiss = itemView.requireViewById<ViewGroup>(R.id.dismiss)
- val dismissLabel = dismiss.getChildAt(0)
+ val dismissText = itemView.requireViewById<TextView>(R.id.dismiss_text)
val settings = itemView.requireViewById<View>(R.id.settings)
val settingsText = itemView.requireViewById<TextView>(R.id.settings_text)
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index a1faa40..20b2d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -20,7 +20,6 @@
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
-import android.widget.ImageView
import android.widget.TextView
import com.android.systemui.R
@@ -29,9 +28,6 @@
*/
class PlayerViewHolder private constructor(itemView: View) : MediaViewHolder(itemView) {
- // Player information
- val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
-
// Seek bar
val progressTimes = itemView.requireViewById<ViewGroup>(R.id.notification_media_progress_time)
override val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time)
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 4baef3a..8684509 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -25,11 +25,13 @@
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostStatesManager;
+import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
+import com.android.systemui.media.nearby.NearbyMediaDevicesService;
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;
@@ -43,11 +45,14 @@
import dagger.multibindings.IntoMap;
/** Dagger module for the media package. */
-@Module
+@Module(subcomponents = {
+ MediaComplicationComponent.class,
+})
public interface MediaModule {
String QS_PANEL = "media_qs_panel";
String QUICK_QS_PANEL = "media_quick_qs_panel";
String KEYGUARD = "media_keyguard";
+ String DREAM = "dream";
/** */
@Provides
@@ -82,14 +87,25 @@
/** */
@Provides
@SysUISingleton
+ @Named(DREAM)
+ static MediaHost providesDreamMediaHost(MediaHost.MediaHostStateHolder stateHolder,
+ MediaHierarchyManager hierarchyManager, MediaDataManager dataManager,
+ MediaHostStatesManager statesManager) {
+ return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
+ }
+
+ /** */
+ @Provides
+ @SysUISingleton
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));
}
/** */
@@ -123,9 +139,9 @@
mediaTttChipControllerReceiver));
}
- /** Inject into MediaTttSenderService. */
+ /** Inject into NearbyMediaDevicesService. */
@Binds
@IntoMap
- @ClassKey(MediaTttSenderService.class)
- Service bindMediaTttSenderService(MediaTttSenderService service);
+ @ClassKey(NearbyMediaDevicesService.class)
+ Service bindMediaNearbyDevicesService(NearbyMediaDevicesService service);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
new file mode 100644
index 0000000..65c5bc7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
@@ -0,0 +1,68 @@
+/*
+ * 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.dream;
+
+import static com.android.systemui.media.dagger.MediaModule.DREAM;
+import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_CONTAINER;
+
+import android.widget.FrameLayout;
+
+import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.MediaHostState;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link MediaComplicationViewController} handles connecting the
+ * {@link com.android.systemui.dreams.complication.Complication} view to the {@link MediaHost}.
+ */
+public class MediaComplicationViewController extends ViewController<FrameLayout> {
+ private final MediaHost mMediaHost;
+
+ @Inject
+ public MediaComplicationViewController(
+ @Named(MEDIA_COMPLICATION_CONTAINER) FrameLayout view,
+ @Named(DREAM) MediaHost mediaHost) {
+ super(view);
+ mMediaHost = mediaHost;
+ }
+
+ @Override
+ protected void onInit() {
+ super.onInit();
+ mMediaHost.setExpansion(MediaHostState.COLLAPSED);
+ mMediaHost.setShowsOnlyActiveMedia(true);
+ mMediaHost.setFalsingProtectionNeeded(true);
+ mMediaHost.init(MediaHierarchyManager.LOCATION_DREAM_OVERLAY);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mMediaHost.hostView.setLayoutParams(new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT));
+ mView.addView(mMediaHost.hostView);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mView.removeView(mMediaHost.hostView);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
new file mode 100644
index 0000000..2c35db3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
@@ -0,0 +1,43 @@
+/*
+ * 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.dream;
+
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
+
+import javax.inject.Inject;
+
+/**
+ * Media control complication for dream overlay.
+ */
+public class MediaDreamComplication implements Complication {
+ MediaComplicationComponent.Factory mComponentFactory;
+
+ /**
+ * Default constructor for {@link MediaDreamComplication}.
+ */
+ @Inject
+ public MediaDreamComplication(MediaComplicationComponent.Factory componentFactory) {
+ mComponentFactory = componentFactory;
+ }
+
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return mComponentFactory.create().getViewHolder();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
new file mode 100644
index 0000000..8934cd10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -0,0 +1,97 @@
+/*
+ * 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.dream;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.media.SmartspaceMediaData;
+
+import javax.inject.Inject;
+
+/**
+ * {@link MediaDreamSentinel} is responsible for tracking media state and registering/unregistering
+ * the media complication as appropriate
+ */
+public class MediaDreamSentinel extends CoreStartable {
+ private MediaDataManager.Listener mListener = new MediaDataManager.Listener() {
+ private boolean mAdded;
+ @Override
+ public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {
+ }
+
+ @Override
+ public void onMediaDataRemoved(@NonNull String key) {
+ if (!mAdded) {
+ return;
+ }
+
+ if (mMediaDataManager.hasActiveMedia()) {
+ return;
+ }
+
+ mAdded = false;
+ mDreamOverlayStateController.removeComplication(mComplication);
+ }
+
+ @Override
+ public void onSmartspaceMediaDataLoaded(@NonNull String key,
+ @NonNull SmartspaceMediaData data, boolean shouldPrioritize,
+ boolean isSsReactivated) {
+ }
+
+ @Override
+ public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey,
+ @NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency) {
+ if (mAdded) {
+ return;
+ }
+
+ if (!mMediaDataManager.hasActiveMedia()) {
+ return;
+ }
+
+ mAdded = true;
+ mDreamOverlayStateController.addComplication(mComplication);
+ }
+ };
+
+ private final MediaDataManager mMediaDataManager;
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final MediaDreamComplication mComplication;
+
+ @Inject
+ public MediaDreamSentinel(Context context, MediaDataManager mediaDataManager,
+ DreamOverlayStateController dreamOverlayStateController,
+ MediaDreamComplication complication) {
+ super(context);
+ mMediaDataManager = mediaDataManager;
+ mDreamOverlayStateController = dreamOverlayStateController;
+ mComplication = complication;
+ }
+
+ @Override
+ public void start() {
+ mMediaDataManager.addListener(mListener);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java
new file mode 100644
index 0000000..128a38c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java
@@ -0,0 +1,58 @@
+/*
+ * 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.dream;
+
+import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_CONTAINER;
+import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_LAYOUT_PARAMS;
+
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link Complication.ViewHolder} implementation for media control.
+ */
+public class MediaViewHolder implements Complication.ViewHolder {
+ private final FrameLayout mContainer;
+ private final MediaComplicationViewController mViewController;
+ private final ComplicationLayoutParams mLayoutParams;
+
+ @Inject
+ MediaViewHolder(@Named(MEDIA_COMPLICATION_CONTAINER) FrameLayout container,
+ MediaComplicationViewController controller,
+ @Named(MEDIA_COMPLICATION_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams) {
+ mContainer = container;
+ mViewController = controller;
+ mViewController.init();
+ mLayoutParams = layoutParams;
+ }
+
+ @Override
+ public View getView() {
+ return mContainer;
+ }
+
+ @Override
+ public ComplicationLayoutParams getLayoutParams() {
+ return mLayoutParams;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java
new file mode 100644
index 0000000..3372899
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java
@@ -0,0 +1,100 @@
+/*
+ * 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.dream.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.content.Context;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.media.dream.MediaViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link MediaComplicationComponent} is responsible for generating dependencies surrounding the
+ * media {@link com.android.systemui.dreams.complication.Complication}, such as view controllers
+ * and layout details.
+ */
+@Subcomponent(modules = {
+ MediaComplicationComponent.MediaComplicationModule.class,
+})
+@MediaComplicationComponent.MediaComplicationScope
+public interface MediaComplicationComponent {
+ @Documented
+ @Retention(RUNTIME)
+ @Scope
+ @interface MediaComplicationScope {}
+
+ /**
+ * Generates {@link MediaComplicationComponent}.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ MediaComplicationComponent create();
+ }
+
+ /**
+ * Creates {@link MediaViewHolder}.
+ */
+ MediaViewHolder getViewHolder();
+
+ /**
+ * Scoped values for {@link MediaComplicationComponent}.
+ */
+ @Module
+ interface MediaComplicationModule {
+ String MEDIA_COMPLICATION_CONTAINER = "media_complication_container";
+ String MEDIA_COMPLICATION_LAYOUT_PARAMS = "media_complication_layout_params";
+
+ /**
+ * Provides the complication view.
+ */
+ @Provides
+ @MediaComplicationScope
+ @Named(MEDIA_COMPLICATION_CONTAINER)
+ static FrameLayout provideComplicationContainer(Context context) {
+ return new FrameLayout(context);
+ }
+
+ /**
+ * Provides the layout parameters for the complication view.
+ */
+ @Provides
+ @MediaComplicationScope
+ @Named(MEDIA_COMPLICATION_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_UP,
+ 0,
+ true);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt
deleted file mode 100644
index 0453fdb..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt
+++ /dev/null
@@ -1,51 +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.nearby
-
-import com.android.systemui.dagger.SysUISingleton
-
-/**
- * A manager that returns information about devices that are nearby and can receive media transfers.
- */
-@SysUISingleton
-class MediaNearbyDevicesManager {
-
- /** Returns a list containing the current nearby devices. */
- fun getCurrentNearbyDevices(): List<NearbyDevice> {
- // TODO(b/216313420): Implement this function.
- return emptyList()
- }
-
- /**
- * Registers [callback] to be notified each time a device's range changes or when a new device
- * comes within range.
- */
- fun registerNearbyDevicesCallback(
- callback: (device: NearbyDevice) -> Unit
- ) {
- // TODO(b/216313420): Implement this function.
- }
-
- /**
- * Un-registers [callback]. See [registerNearbyDevicesCallback].
- */
- fun unregisterNearbyDevicesCallback(
- callback: (device: NearbyDevice) -> Unit
- ) {
- // TODO(b/216313420): Implement this function.
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesService.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesService.kt
new file mode 100644
index 0000000..eaf2bd9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesService.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.nearby
+
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.media.INearbyMediaDevicesProvider
+import com.android.systemui.shared.media.INearbyMediaDevicesService
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback
+import com.android.systemui.shared.media.NearbyDevice
+import javax.inject.Inject
+
+/**
+ * A service that acts as a bridge between (1) external clients that have data on nearby devices
+ * that are able to play media and (2) internal clients (like media Output Switcher) that need data
+ * on these nearby devices.
+ *
+ * TODO(b/216313420): Add logging to this class.
+ */
+@SysUISingleton
+class NearbyMediaDevicesService @Inject constructor() : Service() {
+
+ private var provider: INearbyMediaDevicesProvider? = null
+
+ private val binder: IBinder = object : INearbyMediaDevicesService.Stub() {
+ override fun registerProvider(newProvider: INearbyMediaDevicesProvider) {
+ provider = newProvider
+ newProvider.asBinder().linkToDeath(
+ {
+ // We might've gotten a new provider before the old provider died, so we only
+ // need to clear our provider if the most recent provider died.
+ if (provider == newProvider) {
+ provider = null
+ }
+ },
+ /* flags= */ 0
+ )
+ }
+ }
+
+ override fun onBind(intent: Intent?): IBinder = binder
+
+ /** Returns a list containing the current nearby devices. */
+ fun getCurrentNearbyDevices(): List<NearbyDevice> {
+ val currentProvider = provider ?: return emptyList()
+ return currentProvider.currentNearbyDevices
+ }
+
+ /**
+ * Registers [callback] to be notified each time a device's range changes or when a new device
+ * comes within range.
+ */
+ fun registerNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
+ val currentProvider = provider ?: return
+ currentProvider.registerNearbyDevicesCallback(callback)
+ }
+
+ /**
+ * Un-registers [callback]. See [registerNearbyDevicesCallback].
+ */
+ fun unregisterNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
+ val currentProvider = provider ?: return
+ currentProvider.unregisterNearbyDevicesCallback(callback)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt
deleted file mode 100644
index 3c890bc..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt
+++ /dev/null
@@ -1,44 +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.nearby
-
-import androidx.annotation.IntDef
-import kotlin.annotation.AnnotationRetention
-
-@IntDef(
- RangeZone.RANGE_UNKNOWN,
- RangeZone.RANGE_FAR,
- RangeZone.RANGE_LONG,
- RangeZone.RANGE_CLOSE,
- RangeZone.RANGE_WITHIN_REACH
-)
-@Retention(AnnotationRetention.SOURCE)
-/** The various range zones a device can be in, in relation to the current device. */
-annotation class RangeZone {
- companion object {
- /** Unknown distance range. */
- const val RANGE_UNKNOWN = 0
- /** Distance is very far away from the peer device. */
- const val RANGE_FAR = 1
- /** Distance is relatively long from the peer device, typically a few meters. */
- const val RANGE_LONG = 2
- /** Distance is close to the peer device, typically with one or two meter. */
- const val RANGE_CLOSE = 3
- /** Distance is very close to the peer device, typically within one meter or less. */
- const val RANGE_WITHIN_REACH = 4
- }
-}
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 3720851..bbcbfba 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.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 @@
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 @@
/** 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)
+ val routeInfo = MediaRoute2Info.Builder("id", args[0])
+ .addFeature("feature")
+ .build()
- 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 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 @@
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 c656df2..118a04c 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 @@
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 @@
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 @@
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 453e3d6..c510e35 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 @@
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 717752e..0000000
--- 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/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 4f4bd1e..be45a62 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -104,7 +104,8 @@
private static final int MAX_NUM_LOGGED_PREDICTIONS = 10;
private static final int MAX_NUM_LOGGED_GESTURES = 10;
- static final boolean DEBUG_MISSING_GESTURE = false;
+ // Temporary log until b/202433017 is resolved
+ static final boolean DEBUG_MISSING_GESTURE = true;
static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
private ISystemGestureExclusionListener mGestureExclusionListener =
@@ -318,7 +319,11 @@
String recentsPackageName = recentsComponentName.getPackageName();
PackageManager manager = context.getPackageManager();
try {
- Resources resources = manager.getResourcesForApplication(recentsPackageName);
+ Resources resources = manager.getResourcesForApplication(
+ manager.getApplicationInfo(recentsPackageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.GET_SHARED_LIBRARY_FILES));
int resId = resources.getIdentifier(
"gesture_blocking_activities", "array", recentsPackageName);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 7ac9205..4aedbc9 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.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 @@
* 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 @@
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 @@
}
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 @@
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 @@
}
}
- 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 @@
}
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 7694be5..0000000
--- 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 e6fa2ae..18e0cfa 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 @@
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 @@
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 @@
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 ded6ae0..d1b569f 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.concurrent.Executor;
import javax.inject.Inject;
-import javax.inject.Named;
/** */
@QSScope
@@ -88,8 +84,6 @@
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 @@
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 @@
.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 @@
}
}
- 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 e230e1b..7800027 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 @@
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 @@
*/
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 @@
}
@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 @@
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 @@
}
}
- 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 0e0681b..aac5672 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 @@
*/
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 4622660..6c0ca49 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 @@
public class QSFooterView extends FrameLayout {
private PageIndicator mPageIndicator;
private TextView mBuildText;
- private View mActionsContainer;
private View mEditButton;
@Nullable
@@ -78,7 +77,6 @@
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 @@
}
}
- void updateExpansion() {
- setExpansion(mExpansionAmount);
- }
-
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -129,7 +123,6 @@
@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 5327b7e..bef4f43 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.util.ViewController;
import javax.inject.Inject;
-import javax.inject.Named;
/**
* Controller for {@link QSFooterView}.
@@ -43,8 +40,6 @@
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 @@
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 @@
}
@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 @@
}
@Override
- protected void onViewDetached() {
- setListening(false);
- }
+ protected void onViewDetached() {}
@Override
public void setVisibility(int visibility) {
@@ -126,25 +101,17 @@
@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 @@
@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 259b786..50952bd 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 @@
private QSPanelController mQSPanelController;
private QuickQSPanelController mQuickQSPanelController;
private QSCustomizerController mQSCustomizerController;
+ private FooterActionsController mQSFooterActionController;
@Nullable
private ScrollListener mScrollListener;
/**
@@ -188,9 +189,11 @@
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 @@
mContainer.disable(state1, state2, animate);
mHeader.disable(state1, state2, animate);
mFooter.disable(state1, state2, animate);
+ mQSFooterActionController.disable(state2);
updateQsState();
}
@@ -396,10 +400,10 @@
: 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 @@
}
mFooter.setKeyguardShowing(keyguardShowing);
+ mQSFooterActionController.setKeyguardShowing(keyguardShowing);
updateQsState();
}
@@ -480,14 +485,13 @@
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 @@
}
}
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 @@
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 92690c7d..2d2fa1f 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 @@
// 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 @@
@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 @@
mMediaHost.setShowsOnlyActiveMedia(true);
mMediaHost.init(MediaHierarchyManager.LOCATION_QQS);
mBrightnessController.init(mShouldUseSplitNotificationShade);
- mFooterActionsController.init();
- mFooterActionsController.refreshVisibility(mShouldUseSplitNotificationShade);
}
@Override
@@ -102,7 +96,6 @@
void setListening(boolean listening) {
super.setListening(listening);
mBrightnessController.setListening(listening);
- mFooterActionsController.setListening(listening);
}
public boolean isListening() {
@@ -123,7 +116,6 @@
@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 63cbc21..594f4f8 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 @@
/** 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 1958caf..776ee10 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 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 @@
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 @@
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/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
index dec5afd..c4ea67e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
@@ -16,6 +16,8 @@
package com.android.systemui.screenshot;
+import static java.util.Objects.requireNonNull;
+
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.drawable.Icon;
@@ -28,10 +30,11 @@
import com.android.systemui.R;
+
/**
* View for a chip with an icon and text.
*/
-public class ScreenshotActionChip extends FrameLayout {
+public class OverlayActionChip extends FrameLayout {
private static final String TAG = "ScreenshotActionChip";
@@ -39,27 +42,27 @@
private TextView mTextView;
private boolean mIsPending = false;
- public ScreenshotActionChip(Context context) {
+ public OverlayActionChip(Context context) {
this(context, null);
}
- public ScreenshotActionChip(Context context, AttributeSet attrs) {
+ public OverlayActionChip(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
- public ScreenshotActionChip(Context context, AttributeSet attrs, int defStyleAttr) {
+ public OverlayActionChip(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
- public ScreenshotActionChip(
+ public OverlayActionChip(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onFinishInflate() {
- mIconView = findViewById(R.id.screenshot_action_chip_icon);
- mTextView = findViewById(R.id.screenshot_action_chip_text);
+ mIconView = requireNonNull(findViewById(R.id.overlay_action_chip_icon));
+ mTextView = requireNonNull(findViewById(R.id.overlay_action_chip_text));
updatePadding(mTextView.getText().length() > 0);
}
@@ -116,15 +119,15 @@
(LinearLayout.LayoutParams) mTextView.getLayoutParams();
if (hasText) {
int paddingHorizontal = mContext.getResources().getDimensionPixelSize(
- R.dimen.screenshot_action_chip_padding_horizontal);
+ R.dimen.overlay_action_chip_padding_horizontal);
int spacing = mContext.getResources().getDimensionPixelSize(
- R.dimen.screenshot_action_chip_spacing);
+ R.dimen.overlay_action_chip_spacing);
iconParams.setMarginStart(paddingHorizontal);
iconParams.setMarginEnd(spacing);
textParams.setMarginEnd(paddingHorizontal);
} else {
int paddingHorizontal = mContext.getResources().getDimensionPixelSize(
- R.dimen.screenshot_action_chip_icon_only_padding_horizontal);
+ R.dimen.overlay_action_chip_icon_only_padding_horizontal);
iconParams.setMarginStart(paddingHorizontal);
iconParams.setMarginEnd(paddingHorizontal);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 83d8d19..30456a8 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 @@
};
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/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index e5649a1..f982790 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -131,9 +131,9 @@
private final Resources mResources;
private final Interpolator mFastOutSlowIn;
private final DisplayMetrics mDisplayMetrics;
- private final float mCornerSizeX;
- private final float mDismissDeltaY;
+ private final float mFixedSize;
private final AccessibilityManager mAccessibilityManager;
+ private final GestureDetector mSwipeDetector;
private int mNavMode;
private boolean mOrientationPortrait;
@@ -151,23 +151,21 @@
private LinearLayout mActionsView;
private ImageView mBackgroundProtection;
private FrameLayout mDismissButton;
- private ScreenshotActionChip mShareChip;
- private ScreenshotActionChip mEditChip;
- private ScreenshotActionChip mScrollChip;
- private ScreenshotActionChip mQuickShareChip;
+ private OverlayActionChip mShareChip;
+ private OverlayActionChip mEditChip;
+ private OverlayActionChip mScrollChip;
+ private OverlayActionChip mQuickShareChip;
private UiEventLogger mUiEventLogger;
private ScreenshotViewCallback mCallbacks;
- private Animator mDismissAnimation;
private boolean mPendingSharedTransition;
- private GestureDetector mSwipeDetector;
private SwipeDismissHandler mSwipeDismissHandler;
private InputMonitorCompat mInputMonitor;
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
private boolean mShowScrollablePreview;
private String mPackageName = "";
- private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>();
+ private final ArrayList<OverlayActionChip> mSmartChips = new ArrayList<>();
private PendingInteraction mPendingInteraction;
private enum PendingInteraction {
@@ -194,9 +192,7 @@
super(context, attrs, defStyleAttr, defStyleRes);
mResources = mContext.getResources();
- mCornerSizeX = mResources.getDimensionPixelSize(R.dimen.screenshot_x_scale);
- mDismissDeltaY = mResources.getDimensionPixelSize(
- R.dimen.screenshot_dismissal_height_delta);
+ mFixedSize = mResources.getDimensionPixelSize(R.dimen.overlay_x_scale);
// standard material ease
mFastOutSlowIn =
@@ -474,16 +470,14 @@
int orientation = mContext.getResources().getConfiguration().orientation;
mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
updateInsets(insets);
- int screenshotFixedSize =
- mContext.getResources().getDimensionPixelSize(R.dimen.screenshot_x_scale);
ViewGroup.LayoutParams params = mScreenshotPreview.getLayoutParams();
if (mOrientationPortrait) {
- params.width = screenshotFixedSize;
+ params.width = (int) mFixedSize;
params.height = LayoutParams.WRAP_CONTENT;
mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_START);
} else {
params.width = LayoutParams.WRAP_CONTENT;
- params.height = screenshotFixedSize;
+ params.height = (int) mFixedSize;
mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_END);
}
@@ -500,7 +494,7 @@
// ratio of preview width, end vs. start size
float cornerScale =
- mCornerSizeX / (mOrientationPortrait ? bounds.width() : bounds.height());
+ mFixedSize / (mOrientationPortrait ? bounds.width() : bounds.height());
final float currentScale = 1 / cornerScale;
AnimatorSet dropInAnimation = new AnimatorSet();
@@ -651,7 +645,7 @@
} catch (RemoteException e) {
}
- ArrayList<ScreenshotActionChip> chips = new ArrayList<>();
+ ArrayList<OverlayActionChip> chips = new ArrayList<>();
mShareChip.setContentDescription(mContext.getString(R.string.screenshot_share_description));
mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
@@ -716,7 +710,7 @@
+ (t * (1 - SCREENSHOT_ACTIONS_START_SCALE_X));
mActionsContainer.setScaleX(containerScale);
mActionsContainerBackground.setScaleX(containerScale);
- for (ScreenshotActionChip chip : chips) {
+ for (OverlayActionChip chip : chips) {
chip.setAlpha(t);
chip.setScaleX(1 / containerScale); // invert to keep size of children constant
}
@@ -772,8 +766,8 @@
LayoutInflater inflater = LayoutInflater.from(mContext);
for (Notification.Action smartAction : imageData.smartActions) {
- ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
- R.layout.screenshot_action_chip, mActionsView, false);
+ OverlayActionChip actionChip = (OverlayActionChip) inflater.inflate(
+ R.layout.overlay_action_chip, mActionsView, false);
actionChip.setText(smartAction.title);
actionChip.setIcon(smartAction.getIcon(), false);
actionChip.setPendingIntent(smartAction.actionIntent,
@@ -792,8 +786,8 @@
void addQuickShareChip(Notification.Action quickShareAction) {
if (mPendingInteraction == null) {
LayoutInflater inflater = LayoutInflater.from(mContext);
- mQuickShareChip = (ScreenshotActionChip) inflater.inflate(
- R.layout.screenshot_action_chip, mActionsView, false);
+ mQuickShareChip = (OverlayActionChip) inflater.inflate(
+ R.layout.overlay_action_chip, mActionsView, false);
mQuickShareChip.setText(quickShareAction.title);
mQuickShareChip.setIcon(quickShareAction.getIcon(), false);
mQuickShareChip.setOnClickListener(v -> {
@@ -894,7 +888,7 @@
if (mShowScrollablePreview) {
Rect scrollableArea = scrollableAreaOnScreen(response);
- float scale = mCornerSizeX
+ float scale = mFixedSize
/ (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight());
ConstraintLayout.LayoutParams params =
(ConstraintLayout.LayoutParams) mScrollablePreview.getLayoutParams();
@@ -945,7 +939,7 @@
}
boolean isDismissing() {
- return (mDismissAnimation != null && mDismissAnimation.isRunning());
+ return mSwipeDismissHandler.isDismissing();
}
boolean isPendingSharedTransition() {
@@ -961,12 +955,6 @@
Log.d(TAG, "reset screenshot view");
}
- if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
- if (DEBUG_ANIM) {
- Log.d(TAG, "cancelling dismiss animation");
- }
- mDismissAnimation.cancel();
- }
mSwipeDismissHandler.cancel();
if (DEBUG_WINDOW) {
Log.d(TAG, "removing OnComputeInternalInsetsListener");
@@ -994,7 +982,7 @@
mShareChip.setIsPending(false);
mEditChip.setIsPending(false);
mPendingInteraction = null;
- for (ScreenshotActionChip chip : mSmartChips) {
+ for (OverlayActionChip chip : mSmartChips) {
mActionsView.removeView(chip);
}
mSmartChips.clear();
@@ -1019,31 +1007,6 @@
}
}
- private AnimatorSet createScreenshotTranslateDismissAnimation() {
- ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
- alphaAnim.setStartDelay(SCREENSHOT_DISMISS_ALPHA_OFFSET_MS);
- alphaAnim.setDuration(SCREENSHOT_DISMISS_ALPHA_DURATION_MS);
- alphaAnim.addUpdateListener(animation -> {
- setAlpha(1 - animation.getAnimatedFraction());
- });
-
- ValueAnimator xAnim = ValueAnimator.ofFloat(0, 1);
- xAnim.setInterpolator(mAccelerateInterpolator);
- xAnim.setDuration(SCREENSHOT_DISMISS_X_DURATION_MS);
- float deltaX = mDirectionLTR
- ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth())
- : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX());
- xAnim.addUpdateListener(animation -> {
- float currXDelta = MathUtils.lerp(0, deltaX, animation.getAnimatedFraction());
- mScreenshotStatic.setTranslationX(currXDelta);
- });
-
- AnimatorSet animSet = new AnimatorSet();
- animSet.play(xAnim).with(alphaAnim);
-
- return animSet;
- }
-
ValueAnimator createScreenshotFadeDismissAnimation() {
ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
alphaAnim.addUpdateListener(animation -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
index 4e96003..451fb13 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
@@ -16,6 +16,7 @@
package com.android.systemui.screenshot;
+import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import android.animation.Animator;
@@ -137,10 +138,20 @@
}
/**
+ * Return whether the view is currently being dismissed
+ */
+ public boolean isDismissing() {
+ return (mDismissAnimation != null && mDismissAnimation.isRunning());
+ }
+
+ /**
* Cancel the currently-running dismissal animation, if any.
*/
public void cancel() {
- if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
+ if (isDismissing()) {
+ if (DEBUG_ANIM) {
+ Log.d(TAG, "cancelling dismiss animation");
+ }
mDismissAnimation.cancel();
}
}
@@ -182,7 +193,13 @@
// make sure the UI gets all the way off the screen in the direction of movement
// (the actions container background is guaranteed to be both the leftmost and
// rightmost UI element in LTR and RTL)
- float finalX = startX <= 0 ? -1 * mView.getRight() : mDisplayMetrics.widthPixels;
+ float finalX;
+ int layoutDir = mView.getContext().getResources().getConfiguration().getLayoutDirection();
+ if (startX > 0 || (startX == 0 && layoutDir == View.LAYOUT_DIRECTION_RTL)) {
+ finalX = mDisplayMetrics.widthPixels;
+ } else {
+ finalX = -1 * mView.getRight();
+ }
float distance = Math.abs(finalX - startX);
anim.addUpdateListener(animation -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 597e424..2f5eaa6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -37,11 +37,13 @@
import android.graphics.drawable.Icon;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
+import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
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;
@@ -62,6 +64,7 @@
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;
@@ -154,6 +157,9 @@
private static final int MSG_SET_UDFPS_HBM_LISTENER = 60 << MSG_SHIFT;
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;
@@ -317,6 +323,12 @@
}
/**
+ * @see IStatusBar#setBiometicContextListener(IBiometricContextListener)
+ */
+ default void setBiometicContextListener(IBiometricContextListener listener) {
+ }
+
+ /**
* @see IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
*/
default void setUdfpsHbmListener(IUdfpsHbmListener listener) {
@@ -431,6 +443,17 @@
* @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) {
@@ -958,6 +981,13 @@
}
@Override
+ public void setBiometicContextListener(IBiometricContextListener listener) {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_SET_BIOMETRICS_LISTENER, listener).sendToTarget();
+ }
+ }
+
+ @Override
public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
synchronized (mLock) {
mHandler.obtainMessage(MSG_SET_UDFPS_HBM_LISTENER, listener).sendToTarget();
@@ -1162,6 +1192,29 @@
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);
@@ -1411,6 +1464,12 @@
mCallbacks.get(i).hideAuthenticationDialog();
}
break;
+ case MSG_SET_BIOMETRICS_LISTENER:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).setBiometicContextListener(
+ (IBiometricContextListener) msg.obj);
+ }
+ break;
case MSG_SET_UDFPS_HBM_LISTENER:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).setUdfpsHbmListener((IUdfpsHbmListener) msg.obj);
@@ -1553,6 +1612,29 @@
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/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index c136d9c..b312ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -337,11 +337,11 @@
if (field != value || forceApplyAmount) {
field = value
if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) {
- nsslController.setTransitionToFullShadeAmount(field)
+ qSDragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
+ nsslController.setTransitionToFullShadeAmount(field, qSDragProgress)
+ qS.setTransitionToFullShadeAmount(field, qSDragProgress)
notificationPanelController.setTransitionToFullShadeAmount(field,
false /* animate */, 0 /* delay */)
- qSDragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
- qS.setTransitionToFullShadeAmount(field, qSDragProgress)
// TODO: appear media also in split shade
val mediaAmount = if (useSplitShade) 0f else field
mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 51a66aa..3411eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static com.android.systemui.statusbar.phone.NotificationIconContainer.MAX_ICONS_ON_LOCKSCREEN;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -32,6 +34,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -45,6 +48,7 @@
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.util.Utils;
/**
* A notification shelf view that is placed inside the notification scroller. It manages the
@@ -81,6 +85,11 @@
private int mIndexOfFirstViewInShelf = -1;
private float mCornerAnimationDistance;
private NotificationShelfController mController;
+ private int mActualWidth = -1;
+ private boolean mUseSplitShade;
+
+ /** Fraction of lockscreen to shade animation (on lockscreen swipe down). */
+ private float mFractionToShade;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -122,13 +131,16 @@
layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
setLayoutParams(layoutParams);
- int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
+ final int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
mShelfIcons.setPadding(padding, 0, padding, 0);
mScrollFastThreshold = res.getDimensionPixelOffset(R.dimen.scroll_fast_threshold);
mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
mCornerAnimationDistance = res.getDimensionPixelSize(
R.dimen.notification_corner_animation_distance);
+ // TODO(b/213480466) enable short shelf on split shade
+ mUseSplitShade = Utils.shouldUseSplitNotificationShade(mContext.getResources());
+
mShelfIcons.setInNotificationIconShelf(true);
if (!mShowNotificationShelf) {
setVisibility(GONE);
@@ -203,6 +215,10 @@
final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
viewState.yTranslation = stackEnd - viewState.height;
+
+ final int shortestWidth = mShelfIcons.calculateWidthFor(MAX_ICONS_ON_LOCKSCREEN);
+ final float fraction = Interpolators.STANDARD.getInterpolation(mFractionToShade);
+ updateStateWidth(viewState, fraction, shortestWidth);
} else {
viewState.hidden = true;
viewState.location = ExpandableViewState.LOCATION_GONE;
@@ -211,6 +227,77 @@
}
/**
+ * @param shelfState View state for NotificationShelf
+ * @param fraction Fraction of lockscreen to shade transition
+ * @param shortestWidth Shortest width to use for lockscreen shelf
+ */
+ @VisibleForTesting
+ public void updateStateWidth(ShelfState shelfState, float fraction, int shortestWidth) {
+ shelfState.actualWidth = !mUseSplitShade && mAmbientState.isOnKeyguard()
+ ? (int) MathUtils.lerp(shortestWidth, getWidth(), fraction)
+ : getWidth();
+ }
+
+ /**
+ * @param fractionToShade Fraction of lockscreen to shade transition
+ */
+ public void setFractionToShade(float fractionToShade) {
+ mFractionToShade = fractionToShade;
+ }
+
+ /**
+ * @return Actual width of shelf, accounting for possible ongoing width animation
+ */
+ public int getActualWidth() {
+ return mActualWidth > -1 ? mActualWidth : getWidth();
+ }
+
+ /**
+ * @param localX Click x from left of screen
+ * @param slop Margin of error within which we count x for valid click
+ * @param left Left of shelf, from left of screen
+ * @param right Right of shelf, from left of screen
+ * @return Whether click x was in view
+ */
+ @VisibleForTesting
+ public boolean isXInView(float localX, float slop, float left, float right) {
+ return (left - slop) <= localX && localX < (right + slop);
+ }
+
+ /**
+ * @param localY Click y from top of shelf
+ * @param slop Margin of error within which we count y for valid click
+ * @param top Top of shelf
+ * @param bottom Height of shelf
+ * @return Whether click y was in view
+ */
+ @VisibleForTesting
+ public boolean isYInView(float localY, float slop, float top, float bottom) {
+ return (top - slop) <= localY && localY < (bottom + slop);
+ }
+
+ /**
+ * @param localX Click x
+ * @param localY Click y
+ * @param slop Margin of error for valid click
+ * @return Whether this click was on the visible (non-clipped) part of the shelf
+ */
+ @Override
+ public boolean pointInView(float localX, float localY, float slop) {
+ final float containerWidth = getWidth();
+ final float shelfWidth = getActualWidth();
+
+ final float left = isLayoutRtl() ? containerWidth - shelfWidth : 0;
+ final float right = isLayoutRtl() ? containerWidth : shelfWidth;
+
+ final float top = mClipTopAmount;
+ final float bottom = getActualHeight();
+
+ return isXInView(localX, slop, left, right)
+ && isYInView(localY, slop, top, bottom);
+ }
+
+ /**
* Update the shelf appearance based on the other notifications around it. This transforms
* the icons from the notification area into the shelf.
*/
@@ -732,11 +819,15 @@
// we always want to clip to our sides, such that nothing can draw outside of these bounds
int height = getResources().getDisplayMetrics().heightPixels;
mClipRect.set(0, -height, getWidth(), height);
- mShelfIcons.setClipBounds(mClipRect);
+ if (mShelfIcons != null) {
+ mShelfIcons.setClipBounds(mClipRect);
+ }
}
private void updateRelativeOffset() {
- mCollapsedIcons.getLocationOnScreen(mTmp);
+ if (mCollapsedIcons != null) {
+ mCollapsedIcons.getLocationOnScreen(mTmp);
+ }
getLocationOnScreen(mTmp);
}
@@ -831,9 +922,20 @@
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
}
- private class ShelfState extends ExpandableViewState {
+ public class ShelfState extends ExpandableViewState {
private boolean hasItemsInStableShelf;
private ExpandableView firstViewInShelf;
+ public int actualWidth = -1;
+
+ private void updateShelfWidth(View view) {
+ if (actualWidth < 0) {
+ return;
+ }
+ mActualWidth = actualWidth;
+ ActivatableNotificationView anv = (ActivatableNotificationView) view;
+ anv.getBackgroundNormal().setActualWidth(actualWidth);
+ mShelfIcons.setActualLayoutWidth(actualWidth);
+ }
@Override
public void applyToView(View view) {
@@ -846,19 +948,21 @@
updateAppearance();
setHasItemsInStableShelf(hasItemsInStableShelf);
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
+ updateShelfWidth(view);
}
@Override
- public void animateTo(View child, AnimationProperties properties) {
+ public void animateTo(View view, AnimationProperties properties) {
if (!mShowNotificationShelf) {
return;
}
- super.animateTo(child, properties);
+ super.animateTo(view, properties);
setIndexOfFirstViewInShelf(firstViewInShelf);
updateAppearance();
setHasItemsInStableShelf(hasItemsInStableShelf);
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
+ updateShelfWidth(view);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 1a1003d..092e86d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -27,6 +27,7 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -47,6 +48,7 @@
import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert;
import com.android.wm.shell.bubbles.Bubbles;
@@ -98,6 +100,8 @@
private final ForegroundServiceSectionController mFgsSectionController;
private final NotifPipelineFlags mNotifPipelineFlags;
private AssistantFeedbackController mAssistantFeedbackController;
+ private final KeyguardStateController mKeyguardStateController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final Context mContext;
private NotificationPresenter mPresenter;
@@ -129,7 +133,9 @@
DynamicChildBindController dynamicChildBindController,
LowPriorityInflationHelper lowPriorityInflationHelper,
AssistantFeedbackController assistantFeedbackController,
- NotifPipelineFlags notifPipelineFlags) {
+ NotifPipelineFlags notifPipelineFlags,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ KeyguardStateController keyguardStateController) {
mContext = context;
mHandler = mainHandler;
mFeatureFlags = featureFlags;
@@ -149,6 +155,8 @@
mDynamicChildBindController = dynamicChildBindController;
mLowPriorityInflationHelper = lowPriorityInflationHelper;
mAssistantFeedbackController = assistantFeedbackController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardStateController = keyguardStateController;
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -175,6 +183,11 @@
beginUpdate();
+ boolean dynamicallyUnlocked = mDynamicPrivacyController.isDynamicallyUnlocked()
+ && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
+ && mKeyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
+ KeyguardUpdateMonitor.getCurrentUser()))
+ && !mKeyguardStateController.isKeyguardGoingAway();
List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications();
ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
final int N = activeNotifications.size();
@@ -193,7 +206,7 @@
boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(currentUserId);
boolean userPublic = devicePublic
|| mLockscreenUserManager.isLockscreenPublicMode(userId);
- if (userPublic && mDynamicPrivacyController.isDynamicallyUnlocked()
+ if (userPublic && dynamicallyUnlocked
&& (userId == currentUserId || userId == UserHandle.USER_ALL
|| !mLockscreenUserManager.needsSeparateWorkChallenge(userId))) {
userPublic = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
index c0148c0..16bc951e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
@@ -37,12 +37,6 @@
*/
public static final int SHADE_LOCKED = 2;
- /**
- * Status bar is locked and shows the full screen user switcher.
- */
- public static final int FULLSCREEN_USER_SWITCHER = 3;
-
-
public static String toShortString(int x) {
switch (x) {
case SHADE:
@@ -51,8 +45,6 @@
return "SHD_LCK";
case KEYGUARD:
return "KGRD";
- case FULLSCREEN_USER_SWITCHER:
- return "FS_USRSW";
default:
return "bad_value_" + x;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index bd948ece..ee12cc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -76,7 +76,7 @@
// Must be a power of 2
private static final int HISTORY_SIZE = 32;
- private static final int MAX_STATE = StatusBarState.FULLSCREEN_USER_SWITCHER;
+ private static final int MAX_STATE = StatusBarState.SHADE_LOCKED;
private static final int MIN_STATE = StatusBarState.SHADE;
private static final Comparator<RankedListener> sComparator =
@@ -518,6 +518,7 @@
}
private void recordHistoricalState(int newState, int lastState, boolean upcoming) {
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "statusBarState", newState);
mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
HistoricalState state = mHistoricalRecords[mHistoryIndex];
state.mNewState = newState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java
index 8330169..b66a48e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java
@@ -34,10 +34,7 @@
STATUS_BAR_STATE_KEYGUARD(430),
@UiEvent(doc = "StatusBarState changed to SHADE_LOCKED state")
- STATUS_BAR_STATE_SHADE_LOCKED(431),
-
- @UiEvent(doc = "StatusBarState changed to FULLSCREEN_USER_SWITCHER state")
- STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER(432);
+ STATUS_BAR_STATE_SHADE_LOCKED(431);
private int mId;
StatusBarStateEvent(int id) {
@@ -60,8 +57,6 @@
return STATUS_BAR_STATE_KEYGUARD;
case StatusBarState.SHADE_LOCKED:
return STATUS_BAR_STATE_SHADE_LOCKED;
- case StatusBarState.FULLSCREEN_USER_SWITCHER:
- return STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER;
default:
return STATUS_BAR_STATE_UNKNOWN;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index d574cda..e3d0d98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -23,6 +23,7 @@
import android.service.dreams.IDreamManager;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.SysUISingleton;
@@ -72,6 +73,7 @@
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.tracing.ProtoTracer;
@@ -214,7 +216,9 @@
DynamicChildBindController dynamicChildBindController,
LowPriorityInflationHelper lowPriorityInflationHelper,
AssistantFeedbackController assistantFeedbackController,
- NotifPipelineFlags notifPipelineFlags) {
+ NotifPipelineFlags notifPipelineFlags,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ KeyguardStateController keyguardStateController) {
return new NotificationViewHierarchyManager(
context,
mainHandler,
@@ -231,7 +235,9 @@
dynamicChildBindController,
lowPriorityInflationHelper,
assistantFeedbackController,
- notifPipelineFlags);
+ notifPipelineFlags,
+ keyguardUpdateMonitor,
+ keyguardStateController);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 0ce07cb..22300d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -98,6 +98,8 @@
setupInvalidateNotifListCallbacks();
// Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
pipeline.addFinalizeFilter(mNotifFilter);
+
+ updateSectionHeadersVisibility();
}
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@@ -164,6 +166,8 @@
}
}
+ // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
+ // these same updates
private void setupInvalidateNotifListCallbacks() {
// register onKeyguardShowing callback
mKeyguardStateController.addCallback(mKeyguardCallback);
@@ -220,10 +224,7 @@
}
private void invalidateListFromFilter(String reason) {
- boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
- boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
- boolean showSections = !onKeyguard && !neverShowSections;
- mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
+ updateSectionHeadersVisibility();
mNotifFilter.invalidateList();
}
@@ -235,6 +236,13 @@
1) == 0;
}
+ private void updateSectionHeadersVisibility() {
+ boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+ boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
+ boolean showSections = !onKeyguard && !neverShowSections;
+ mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
+ }
+
private final KeyguardStateController.Callback mKeyguardCallback =
new KeyguardStateController.Callback() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index a115e04..9c82cb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -17,7 +17,10 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.os.UserHandle
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.DynamicPrivacyController
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -26,6 +29,7 @@
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Module
import dagger.Provides
@@ -36,9 +40,13 @@
@CoordinatorScope
fun provideCoordinator(
dynamicPrivacyController: DynamicPrivacyController,
- lockscreenUserManager: NotificationLockscreenUserManager
+ lockscreenUserManager: NotificationLockscreenUserManager,
+ keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ statusBarStateController: StatusBarStateController,
+ keyguardStateController: KeyguardStateController
): SensitiveContentCoordinator =
- SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager)
+ SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager,
+ keyguardUpdateMonitor, statusBarStateController, keyguardStateController)
}
/** Coordinates re-inflation and post-processing of sensitive notification content. */
@@ -46,7 +54,10 @@
private class SensitiveContentCoordinatorImpl(
private val dynamicPrivacyController: DynamicPrivacyController,
- private val lockscreenUserManager: NotificationLockscreenUserManager
+ private val lockscreenUserManager: NotificationLockscreenUserManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val statusBarStateController: StatusBarStateController,
+ private val keyguardStateController: KeyguardStateController
) : Invalidator("SensitiveContentInvalidator"),
SensitiveContentCoordinator,
DynamicPrivacyController.Listener,
@@ -61,6 +72,19 @@
override fun onDynamicPrivacyChanged(): Unit = invalidateList()
override fun onBeforeRenderList(entries: List<ListEntry>) {
+ if (keyguardStateController.isKeyguardGoingAway() ||
+ statusBarStateController.getState() == StatusBarState.KEYGUARD &&
+ keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
+ KeyguardUpdateMonitor.getCurrentUser())) {
+ // don't update yet if:
+ // - the keyguard is currently going away
+ // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash)
+
+ // TODO(b/206118999): merge this class with KeyguardCoordinator which ensures the
+ // dependent state changes invalidate the pipeline
+ return
+ }
+
val currentUserId = lockscreenUserManager.currentUserId
val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
val deviceSensitive = devicePublic &&
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 28cd285..386e2d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -104,19 +104,14 @@
views.remove(childNode.controller.view)
}
- if (childCompletelyRemoved && parentSpec == null) {
- // If both the child and the parent are being removed at the same time, then
- // keep the child attached to the parent for animation purposes
- logger.logSkippingDetach(childNode.label, parentNode.label)
- } else {
- logger.logDetachingChild(
- childNode.label,
- !childCompletelyRemoved,
- parentNode.label,
- newParentNode?.label)
- parentNode.removeChild(childNode, !childCompletelyRemoved)
- childNode.parent = null
- }
+ logger.logDetachingChild(
+ key = childNode.label,
+ isTransfer = !childCompletelyRemoved,
+ isParentRemoved = parentSpec == null,
+ oldParent = parentNode.label,
+ newParent = newParentNode?.label)
+ parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
+ childNode.parent = null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index d274550..4c03572 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -28,25 +28,18 @@
fun logDetachingChild(
key: String,
isTransfer: Boolean,
+ isParentRemoved: Boolean,
oldParent: String?,
newParent: String?
) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = key
bool1 = isTransfer
+ bool2 = isParentRemoved
str2 = oldParent
str3 = newParent
}, {
- "Detach $str1 isTransfer=$bool1 oldParent=$str2 newParent=$str3"
- })
- }
-
- fun logSkippingDetach(key: String, parent: String?) {
- buffer.log(TAG, LogLevel.DEBUG, {
- str1 = key
- str2 = parent
- }, {
- "Skipping detach of $str1 because its parent $str2 is also being detached"
+ "Detach $str1 isTransfer=$bool1 isParentRemoved=$bool2 oldParent=$str2 newParent=$str3"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 5d6d0f7..fca2aa1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -162,6 +162,13 @@
updateBackgroundTint();
}
+ /**
+ * @return The background of this view.
+ */
+ public NotificationBackgroundView getBackgroundNormal() {
+ return mBackgroundNormal;
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index dbd22db..1f7d930 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -49,6 +49,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.Trace;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
@@ -1246,6 +1247,7 @@
}
private void reInflateViews() {
+ Trace.beginSection("ExpandableNotificationRow#reInflateViews");
// Let's update our childrencontainer. This is intentionally not guarded with
// mIsSummaryWithChildren since we might have had children but not anymore.
if (mChildrenContainer != null) {
@@ -1277,6 +1279,7 @@
RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
params.setNeedsReinflation(true);
mRowContentBindStage.requestRebind(mEntry, null /* callback */);
+ Trace.endSection();
}
@Override
@@ -1737,6 +1740,29 @@
}
@Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure"));
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ Trace.endSection();
+ }
+
+ /** Generates and appends "(MessagingStyle)" type tag to passed string for tracing. */
+ @NonNull
+ private String appendTraceStyleTag(@NonNull String traceTag) {
+ if (!Trace.isEnabled()) {
+ return traceTag;
+ }
+
+ Class<? extends Notification.Style> style =
+ getEntry().getSbn().getNotification().getNotificationStyle();
+ if (style == null) {
+ return traceTag + "(nostyle)";
+ } else {
+ return traceTag + "(" + style.getSimpleName() + ")";
+ }
+ }
+
+ @Override
protected void onFinishInflate() {
super.onFinishInflate();
mPublicLayout = findViewById(R.id.expandedPublic);
@@ -2542,6 +2568,7 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ Trace.beginSection(appendTraceStyleTag("ExpNotRow#onLayout"));
int intrinsicBefore = getIntrinsicHeight();
super.onLayout(changed, left, top, right, bottom);
if (intrinsicBefore != getIntrinsicHeight()
@@ -2555,6 +2582,7 @@
if (mLayoutListener != null) {
mLayoutListener.onLayout();
}
+ Trace.endSection();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 0f615aa..c640ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -29,6 +29,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
/**
@@ -39,15 +40,17 @@
private final boolean mDontModifyCorners;
private Drawable mBackground;
private int mClipTopAmount;
- private int mActualHeight;
private int mClipBottomAmount;
private int mTintColor;
private final float[] mCornerRadii = new float[8];
private boolean mBottomIsRounded;
private int mBackgroundTop;
private boolean mBottomAmountClips = true;
+ private int mActualHeight = -1;
+ private int mActualWidth = -1;
private boolean mExpandAnimationRunning;
- private float mActualWidth;
+ private int mExpandAnimationWidth = -1;
+ private int mExpandAnimationHeight = -1;
private int mDrawableAlpha = 255;
private boolean mIsPressedAllowed;
@@ -59,11 +62,12 @@
@Override
protected void onDraw(Canvas canvas) {
- if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop
+ if (mClipTopAmount + mClipBottomAmount < getActualHeight() - mBackgroundTop
|| mExpandAnimationRunning) {
canvas.save();
if (!mExpandAnimationRunning) {
- canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+ canvas.clipRect(0, mClipTopAmount, getWidth(),
+ getActualHeight() - mClipBottomAmount);
}
draw(canvas, mBackground);
canvas.restore();
@@ -73,17 +77,23 @@
private void draw(Canvas canvas, Drawable drawable) {
if (drawable != null) {
int top = mBackgroundTop;
- int bottom = mActualHeight;
+ int bottom = getActualHeight();
if (mBottomIsRounded
&& mBottomAmountClips
&& !mExpandAnimationRunning) {
bottom -= mClipBottomAmount;
}
- int left = 0;
- int right = getWidth();
+ final boolean isRtl = isLayoutRtl();
+ final int width = getWidth();
+ final int actualWidth = getActualWidth();
+
+ int left = isRtl ? width - actualWidth : 0;
+ int right = isRtl ? width : actualWidth;
+
if (mExpandAnimationRunning) {
- left = (int) ((getWidth() - mActualWidth) / 2.0f);
- right = (int) (left + mActualWidth);
+ // Horizontally center this background view inside of the container
+ left = (int) ((width - actualWidth) / 2.0f);
+ right = (int) (left + actualWidth);
}
drawable.setBounds(left, top, right, bottom);
drawable.draw(canvas);
@@ -152,8 +162,26 @@
invalidate();
}
- public int getActualHeight() {
- return mActualHeight;
+ private int getActualHeight() {
+ if (mExpandAnimationRunning && mExpandAnimationHeight > -1) {
+ return mExpandAnimationHeight;
+ } else if (mActualHeight > -1) {
+ return mActualHeight;
+ }
+ return getHeight();
+ }
+
+ public void setActualWidth(int actualWidth) {
+ mActualWidth = actualWidth;
+ }
+
+ private int getActualWidth() {
+ if (mExpandAnimationRunning && mExpandAnimationWidth > -1) {
+ return mExpandAnimationWidth;
+ } else if (mActualWidth > -1) {
+ return mActualWidth;
+ }
+ return getWidth();
}
public void setClipTopAmount(int clipTopAmount) {
@@ -241,9 +269,9 @@
}
/** Set the current expand animation size. */
- public void setExpandAnimationSize(int actualWidth, int actualHeight) {
- mActualHeight = actualHeight;
- mActualWidth = actualWidth;
+ public void setExpandAnimationSize(int width, int height) {
+ mExpandAnimationHeight = width;
+ mExpandAnimationWidth = height;
invalidate();
}
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 55d66a3..25b8a65 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
@@ -198,7 +198,7 @@
private int mMaxTopPadding;
private int mTopPadding;
private boolean mAnimateNextTopPaddingChange;
- private int mBottomMargin;
+ private int mBottomPadding;
private int mBottomInset = 0;
private float mQsExpansionFraction;
private final int mSplitShadeMinContentHeight;
@@ -979,7 +979,7 @@
mMinTopOverScrollToEscape = res.getDimensionPixelSize(
R.dimen.min_top_overscroll_to_qs);
mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
- mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+ mBottomPadding = res.getDimensionPixelSize(R.dimen.notification_panel_padding_bottom);
mMinimumPaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
mQsTilePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_margin_horizontal);
mSkinnyNotifsInLandscape = res.getBoolean(R.bool.config_skinnyNotifsInLandscape);
@@ -2278,7 +2278,7 @@
// The topPadding can be bigger than the regular padding when qs is expanded, in that
// state the maxPanelHeight and the contentHeight should be bigger
- mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin;
+ mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomPadding;
updateScrollability();
clampScrollPosition();
updateStackPosition();
@@ -5494,6 +5494,17 @@
}
/**
+ * @param fraction Fraction of the lockscreen to shade transition. 0f for all other states.
+ * Once the lockscreen to shade transition completes and the shade is 100% open
+ * LockscreenShadeTransitionController resets fraction to 0
+ * where it remains until the next lockscreen-to-shade transition.
+ */
+ public void setFractionToShade(float fraction) {
+ mShelf.setFractionToShade(fraction);
+ requestChildrenUpdate();
+ }
+
+ /**
* Set a listener to when scrolling changes.
*/
public void setOnScrollListener(Consumer<Integer> listener) {
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 96b1c7d..a2929f0 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
@@ -1538,10 +1538,18 @@
}
/**
- * Set the amount of pixels we have currently dragged down if we're transitioning to the full
- * shade. 0.0f means we're not transitioning yet.
+ * @param amount The amount of pixels we have currently dragged down
+ * for the lockscreen to shade transition. 0f for all other states.
+ * @param fraction The fraction of lockscreen to shade transition.
+ * 0f for all other states.
+ *
+ * Once the lockscreen to shade transition completes and the shade is 100% open,
+ * LockscreenShadeTransitionController resets amount and fraction to 0, where they remain
+ * until the next lockscreen-to-shade transition.
*/
- public void setTransitionToFullShadeAmount(float amount) {
+ public void setTransitionToFullShadeAmount(float amount, float fraction) {
+ mView.setFractionToShade(fraction);
+
float extraTopInset = 0.0f;
if (mStatusBarStateController.getState() == KEYGUARD) {
float overallProgress = MathUtils.saturate(amount / mView.getHeight());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 8f0579c..e24cd3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -46,7 +46,7 @@
*/
public class StackScrollAlgorithm {
- public static final float START_FRACTION = 0.3f;
+ public static final float START_FRACTION = 0.5f;
private static final String LOG_TAG = "StackScrollAlgorithm";
private final ViewGroup mHostView;
@@ -61,7 +61,7 @@
@VisibleForTesting float mHeadsUpInset;
private int mPinnedZTranslationExtra;
private float mNotificationScrimPadding;
- private int mCloseHandleUnderlapHeight;
+ private int mMarginBottom;
public StackScrollAlgorithm(
Context context,
@@ -87,7 +87,7 @@
R.dimen.heads_up_pinned_elevation);
mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
- mCloseHandleUnderlapHeight = res.getDimensionPixelSize(R.dimen.close_handle_underlap);
+ mMarginBottom = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
}
/**
@@ -463,7 +463,7 @@
}
} else {
if (view instanceof EmptyShadeView) {
- float fullHeight = ambientState.getLayoutMaxHeight() + mCloseHandleUnderlapHeight
+ float fullHeight = ambientState.getLayoutMaxHeight() + mMarginBottom
- ambientState.getStackY();
viewState.yTranslation = (fullHeight - getMaxAllowedChildHeight(view)) / 2f;
} else if (view != ambientState.getTrackedHeadsUpRow()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 0d2bddc..7c9df42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -44,6 +44,7 @@
public static final int ANIMATION_DURATION_STANDARD = 360;
public static final int ANIMATION_DURATION_CORNER_RADIUS = 200;
public static final int ANIMATION_DURATION_WAKEUP = 500;
+ public static final int ANIMATION_DURATION_WAKEUP_SCRIM = 667;
public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
public static final int ANIMATION_DURATION_SWIPE = 200;
@@ -343,9 +344,11 @@
for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
final ExpandableView changingView = (ExpandableView) event.mChangingView;
boolean loggable = false;
+ boolean isHeadsUp = false;
String key = null;
if (changingView instanceof ExpandableNotificationRow && mLogger != null) {
loggable = true;
+ isHeadsUp = ((ExpandableNotificationRow) changingView).isHeadsUp();
key = ((ExpandableNotificationRow) changingView).getEntry().getKey();
}
if (event.animationType ==
@@ -357,6 +360,9 @@
// The position for this child was never generated, let's continue.
continue;
}
+ if (loggable && isHeadsUp) {
+ mLogger.logHUNViewAppearingWithAddEvent(key);
+ }
viewState.applyToView(changingView);
mNewAddChildren.add(changingView);
@@ -398,9 +404,18 @@
translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
}
+ Runnable postAnimation = changingView::removeFromTransientContainer;
+ if (loggable && isHeadsUp) {
+ mLogger.logHUNViewDisappearingWithRemoveEvent(key);
+ String finalKey = key;
+ postAnimation = () -> {
+ mLogger.disappearAnimationEnded(finalKey);
+ changingView.removeFromTransientContainer();
+ };
+ }
changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
- 0, changingView::removeFromTransientContainer, null);
+ 0, postAnimation, null);
} else if (event.animationType ==
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
if (mHostLayout.isFullySwipedOut(changingView)) {
@@ -430,8 +445,7 @@
// this only captures HEADS_UP_APPEAR animations, but HUNs can appear with normal
// ADD animations, which would not be logged here.
if (loggable) {
- mLogger.logHUNViewAppearing(
- ((ExpandableNotificationRow) changingView).getEntry().getKey());
+ mLogger.logHUNViewAppearing(key);
}
mTmpState.applyToView(changingView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index 4315265..77377af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -24,6 +24,22 @@
})
}
+ fun logHUNViewDisappearingWithRemoveEvent(key: String) {
+ buffer.log(TAG, LogLevel.ERROR, {
+ str1 = key
+ }, {
+ "Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE"
+ })
+ }
+
+ fun logHUNViewAppearingWithAddEvent(key: String) {
+ buffer.log(TAG, LogLevel.ERROR, {
+ str1 = key
+ }, {
+ "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD"
+ })
+ }
+
fun disappearAnimationEnded(key: String) {
buffer.log(TAG, LogLevel.INFO, {
str1 = key
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 04d3e9a..6a78370 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -180,6 +180,7 @@
private final MetricsLogger mMetricsLogger;
private final AuthController mAuthController;
private final StatusBarStateController mStatusBarStateController;
+ private final LatencyTracker mLatencyTracker;
private long mLastFpFailureUptimeMillis;
private int mNumConsecutiveFpFailures;
@@ -281,7 +282,8 @@
AuthController authController,
StatusBarStateController statusBarStateController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- SessionTracker sessionTracker) {
+ SessionTracker sessionTracker,
+ LatencyTracker latencyTracker) {
mContext = context;
mPowerManager = powerManager;
mShadeController = shadeController;
@@ -289,6 +291,7 @@
mDozeParameters = dozeParameters;
mUpdateMonitor.registerCallback(this);
mMediaManager = notificationMediaManager;
+ mLatencyTracker = latencyTracker;
wakefulnessLifecycle.addObserver(mWakefulnessObserver);
screenLifecycle.addObserver(mScreenObserver);
@@ -343,13 +346,13 @@
public void onBiometricAcquired(BiometricSourceType biometricSourceType) {
Trace.beginSection("BiometricUnlockController#onBiometricAcquired");
releaseBiometricWakeLock();
- if (!mUpdateMonitor.isDeviceInteractive()) {
- if (LatencyTracker.isEnabled(mContext)) {
+ if (isWakeAndUnlock()) {
+ if (mLatencyTracker.isEnabled()) {
int action = LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK;
if (biometricSourceType == BiometricSourceType.FACE) {
action = LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK;
}
- LatencyTracker.getInstance(mContext).onActionStart(action);
+ mLatencyTracker.onActionStart(action);
}
mWakeLock = mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BIOMETRIC_WAKE_LOCK_NAME);
@@ -652,6 +655,14 @@
Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
.ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId()));
+ if (mLatencyTracker.isEnabled()) {
+ int action = LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK;
+ if (biometricSourceType == BiometricSourceType.FACE) {
+ action = LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK;
+ }
+ mLatencyTracker.onActionCancel(action);
+ }
+
if (biometricSourceType == BiometricSourceType.FINGERPRINT
&& mUpdateMonitor.isUdfpsSupported()) {
long currUptimeMillis = SystemClock.uptimeMillis();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 00b54e9..2ec5f25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
import static com.android.systemui.util.Utils.getStatusBarHeaderHeightKeyguard;
@@ -27,7 +26,6 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.UserManager;
import android.util.AttributeSet;
import android.util.Pair;
import android.util.TypedValue;
@@ -47,7 +45,6 @@
import com.android.systemui.animation.Interpolators;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.window.StatusBarWindowView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -72,7 +69,6 @@
private StatusIconContainer mStatusIconContainer;
private boolean mKeyguardUserSwitcherEnabled;
- private final UserManager mUserManager;
private boolean mIsPrivacyDotEnabled;
private int mSystemIconsSwitcherHiddenExpandedMargin;
@@ -99,10 +95,10 @@
*/
private int mTopClipping;
private final Rect mClipRect = new Rect(0, 0, 0, 0);
+ private boolean mIsUserSwitcherEnabled;
public KeyguardStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
- mUserManager = UserManager.get(getContext());
}
@Override
@@ -163,6 +159,10 @@
updateKeyguardStatusBarHeight();
}
+ public void setUserSwitcherEnabled(boolean enabled) {
+ mIsUserSwitcherEnabled = enabled;
+ }
+
private void updateKeyguardStatusBarHeight() {
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
lp.height = getStatusBarHeaderHeightKeyguard(mContext);
@@ -200,11 +200,7 @@
// If we have no keyguard switcher, the screen width is under 600dp. In this case,
// we only show the multi-user switch if it's enabled through UserManager as well as
// by the user.
- // TODO(b/138661450) Move IPC calls to background
- boolean isMultiUserEnabled = whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
- mContext.getResources().getBoolean(
- R.bool.qs_show_user_switcher_for_single_user)));
- if (isMultiUserEnabled) {
+ if (mIsUserSwitcherEnabled) {
mMultiUserAvatar.setVisibility(View.VISIBLE);
} else {
mMultiUserAvatar.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 8187163..ee97fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -23,8 +23,10 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricSourceType;
+import android.os.UserManager;
import android.util.MathUtils;
import android.view.View;
@@ -92,6 +94,7 @@
private final BiometricUnlockController mBiometricUnlockController;
private final SysuiStatusBarStateController mStatusBarStateController;
private final StatusBarContentInsetsProvider mInsetsProvider;
+ private final UserManager mUserManager;
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -105,6 +108,11 @@
mView.onOverlayChanged();
KeyguardStatusBarViewController.this.onThemeChanged();
}
+
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ updateUserSwitcher();
+ }
};
private final SystemStatusAnimationCallback mAnimationCallback =
@@ -159,6 +167,13 @@
}
@Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ if (showing) {
+ updateUserSwitcher();
+ }
+ }
+
+ @Override
public void onBiometricRunningStateChanged(
boolean running,
BiometricSourceType biometricSourceType) {
@@ -230,7 +245,8 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
BiometricUnlockController biometricUnlockController,
SysuiStatusBarStateController statusBarStateController,
- StatusBarContentInsetsProvider statusBarContentInsetsProvider
+ StatusBarContentInsetsProvider statusBarContentInsetsProvider,
+ UserManager userManager
) {
super(view);
mCarrierTextController = carrierTextController;
@@ -248,6 +264,7 @@
mBiometricUnlockController = biometricUnlockController;
mStatusBarStateController = statusBarStateController;
mInsetsProvider = statusBarContentInsetsProvider;
+ mUserManager = userManager;
mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
mKeyguardStateController.addCallback(
@@ -293,7 +310,7 @@
}
mView.setOnApplyWindowInsetsListener(
(view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider));
-
+ updateUserSwitcher();
onThemeChanged();
}
@@ -437,6 +454,14 @@
}
/**
+ * Updates visibility of the user switcher button based on {@link android.os.UserManager} state.
+ */
+ private void updateUserSwitcher() {
+ mView.setUserSwitcherEnabled(mUserManager.isUserSwitcherEnabled(getResources().getBoolean(
+ R.bool.qs_show_user_switcher_for_single_user)));
+ }
+
+ /**
* Update {@link KeyguardStatusBarView}'s visibility based on whether keyguard is showing and
* whether heads up is visible.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index c09c485..ebfed1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -135,7 +135,8 @@
}
}.setDuration(CONTENT_FADE_DURATION);
- private static final int MAX_VISIBLE_ICONS_ON_LOCK = 5;
+ private static final int MAX_ICONS_ON_AOD = 3;
+ public static final int MAX_ICONS_ON_LOCKSCREEN = 3;
public static final int MAX_STATIC_ICONS = 4;
private static final int MAX_DOTS = 1;
@@ -386,6 +387,19 @@
}
/**
+ * @return Width of shelf for the given number of icons and overflow dot
+ */
+ public int calculateWidthFor(int numMaxIcons) {
+ if (getChildCount() == 0) {
+ return 0;
+ }
+ return (int) (getActualPaddingStart()
+ + numMaxIcons * mIconSize
+ + mOverflowWidth
+ + getActualPaddingEnd());
+ }
+
+ /**
* Calculate the horizontal translations for each notification based on how much the icons
* are inserted into the notification container.
* If this is not a whole number, the fraction means by how much the icon is appearing.
@@ -394,7 +408,7 @@
float translationX = getActualPaddingStart();
int firstOverflowIndex = -1;
int childCount = getChildCount();
- int maxVisibleIcons = mOnLockScreen ? MAX_VISIBLE_ICONS_ON_LOCK :
+ int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD :
mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
float layoutEnd = getLayoutEnd();
float overflowStart = getMaxOverflowStart();
@@ -414,7 +428,7 @@
}
boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
&& iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons;
- boolean noOverflowAfter = i == childCount - 1;
+ boolean isLastChild = i == childCount - 1;
float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
? ((StatusBarIconView) view).getIconScaleIncreased()
: 1f;
@@ -423,10 +437,10 @@
: StatusBarIconView.STATE_ICON;
boolean isOverflowing =
- (translationX > (noOverflowAfter ? layoutEnd - mIconSize
+ (translationX > (isLastChild ? layoutEnd - mIconSize
: overflowStart - mIconSize));
if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) {
- firstOverflowIndex = noOverflowAfter && !forceOverflow ? i - 1 : i;
+ firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i;
mVisualOverflowStart = layoutEnd - mOverflowWidth;
if (forceOverflow || mIsStaticLayout) {
mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt
new file mode 100644
index 0000000..ff48755
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.statusbar.phone
+
+import android.content.Context
+import android.view.ViewGroup
+import com.android.systemui.R
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.unfold.SysUIUnfoldScope
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
+import javax.inject.Inject
+
+@SysUIUnfoldScope
+class NotificationPanelUnfoldAnimationController
+@Inject
+constructor(private val context: Context, progressProvider: NaturalRotationUnfoldProgressProvider) {
+
+ private val translateAnimator by lazy {
+ UnfoldConstantTranslateAnimator(
+ viewsIdToTranslate =
+ setOf(
+ ViewIdToTranslate(R.id.quick_settings_panel, LEFT),
+ ViewIdToTranslate(R.id.notification_stack_scroller, RIGHT),
+ ViewIdToTranslate(R.id.rightLayout, RIGHT),
+ ViewIdToTranslate(R.id.clock, LEFT),
+ ViewIdToTranslate(R.id.date, LEFT)),
+ progressProvider = progressProvider)
+ }
+
+ fun setup(root: ViewGroup) {
+ val translationMax =
+ context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings).toFloat()
+ translateAnimator.init(root, translationMax)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 9d3f19a..3d3a1da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -20,6 +20,7 @@
import static android.view.View.GONE;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
import static androidx.constraintlayout.widget.ConstraintSet.END;
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
import static androidx.constraintlayout.widget.ConstraintSet.START;
@@ -414,6 +415,7 @@
private int mDisplayTopInset = 0; // in pixels
private int mDisplayRightInset = 0; // in pixels
private int mSplitShadeStatusBarHeight;
+ private int mSplitShadeNotificationsScrimMarginBottom;
private final KeyguardClockPositionAlgorithm
mClockPositionAlgorithm =
@@ -669,6 +671,8 @@
private boolean mStatusViewCentered = true;
private Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition;
+ private Optional<NotificationPanelUnfoldAnimationController>
+ mNotificationPanelUnfoldAnimationController;
private final ListenerSet<Callbacks> mNotifEventSourceCallbacks = new ListenerSet<>();
@@ -929,6 +933,8 @@
mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count);
mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition());
+ mNotificationPanelUnfoldAnimationController = unfoldComponent.map(
+ SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController);
mCommunalSourceMonitorCallback = (source) -> {
mUiExecutor.execute(() -> setCommunalSource(source));
@@ -1064,6 +1070,8 @@
mTapAgainViewController.init();
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
+ mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
+ controller.setup(mNotificationContainerParent));
}
@Override
@@ -1162,6 +1170,9 @@
public void updateResources() {
mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
mSplitShadeStatusBarHeight = Utils.getSplitShadeStatusBarHeight(mView.getContext());
+ mSplitShadeNotificationsScrimMarginBottom =
+ mResources.getDimensionPixelSize(
+ R.dimen.split_shade_notifications_scrim_margin_bottom);
int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width);
int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
mShouldUseSplitNotificationShade =
@@ -1171,6 +1182,8 @@
mQs.setInSplitShade(mShouldUseSplitNotificationShade);
}
+ int notificationsBottomMargin = mResources.getDimensionPixelSize(
+ R.dimen.notification_panel_margin_bottom);
int topMargin = mShouldUseSplitNotificationShade ? mSplitShadeStatusBarHeight :
mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
mSplitShadeHeaderController.setSplitShadeMode(mShouldUseSplitNotificationShade);
@@ -1199,9 +1212,12 @@
constraintSet.getConstraint(R.id.notification_stack_scroller).layout.mWidth = panelWidth;
constraintSet.getConstraint(R.id.qs_frame).layout.mWidth = qsWidth;
constraintSet.setMargin(R.id.notification_stack_scroller, TOP, topMargin);
+ constraintSet.setMargin(R.id.notification_stack_scroller, BOTTOM,
+ notificationsBottomMargin);
constraintSet.setMargin(R.id.qs_frame, TOP, topMargin);
constraintSet.applyTo(mNotificationContainerParent);
mAmbientState.setStackTopMargin(topMargin);
+ mNotificationsQSContainerController.updateMargins();
mNotificationsQSContainerController.setSplitShadeEnabled(mShouldUseSplitNotificationShade);
updateKeyguardStatusViewAlignment(/* animate= */false);
@@ -1319,6 +1335,7 @@
setKeyguardBottomAreaVisibility(mBarState, false);
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
+ mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
}
private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
@@ -2558,7 +2575,8 @@
right = getView().getRight() + mDisplayRightInset;
} else {
top = Math.min(qsPanelBottomY, mSplitShadeStatusBarHeight);
- bottom = top + mNotificationStackScrollLayoutController.getHeight();
+ bottom = top + mNotificationStackScrollLayoutController.getHeight()
+ - mSplitShadeNotificationsScrimMarginBottom;
left = mNotificationStackScrollLayoutController.getLeft();
right = mNotificationStackScrollLayoutController.getRight();
}
@@ -4188,9 +4206,10 @@
return false;
}
- // Do not allow panel expansion if bouncer is scrimmed, otherwise user would be able
- // to pull down QS or expand the shade.
- if (mStatusBar.isBouncerShowingScrimmed()) {
+ // Do not allow panel expansion if bouncer is scrimmed or showing over a dream,
+ // otherwise user would be able to pull down QS or expand the shade.
+ if (mStatusBar.isBouncerShowingScrimmed()
+ || mStatusBar.isBouncerShowingOverDream()) {
return false;
}
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 34bb6d3..b457ebf 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 @@
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
@@ -63,7 +66,7 @@
}
public override fun onViewAttached() {
- notificationsBottomMargin = mView.defaultNotificationsMarginBottom
+ updateMargins()
overviewProxyService.addCallback(taskbarVisibilityListener)
mView.setInsetsChangedListener(windowInsetsListener)
mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
@@ -75,6 +78,10 @@
mView.removeQSFragmentAttachedListener()
}
+ fun updateMargins() {
+ notificationsBottomMargin = mView.defaultNotificationsMarginBottom
+ }
+
override fun setCustomizerAnimating(animating: Boolean) {
if (isQSCustomizerAnimating != animating) {
isQSCustomizerAnimating = animating
@@ -104,7 +111,11 @@
}
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 9210a8b..95e3b70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -45,7 +45,6 @@
private View mStackScroller;
private View mKeyguardStatusBar;
- private int mStackScrollerMargin;
private ArrayList<View> mDrawingOrderedChildren = new ArrayList<>();
private ArrayList<View> mLayoutDrawingOrder = new ArrayList<>();
private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild);
@@ -53,6 +52,7 @@
private Consumer<QS> mQSFragmentAttachedListener = qs -> {};
private QS mQs;
private View mQSScrollView;
+ private View mQSContainer;
public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -63,7 +63,6 @@
super.onFinishInflate();
mQsFrame = findViewById(R.id.qs_frame);
mStackScroller = findViewById(R.id.notification_stack_scroller);
- mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
mKeyguardStatusBar = findViewById(R.id.keyguard_header);
}
@@ -72,6 +71,7 @@
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
@@ -91,13 +91,23 @@
mQSScrollView.getPaddingLeft(),
mQSScrollView.getPaddingTop(),
mQSScrollView.getPaddingRight(),
+ paddingBottom);
+ }
+ }
+
+ public void setQSContainerPaddingBottom(int paddingBottom) {
+ if (mQSContainer != null) {
+ mQSContainer.setPadding(
+ mQSContainer.getPaddingLeft(),
+ mQSContainer.getPaddingTop(),
+ mQSContainer.getPaddingRight(),
paddingBottom
);
}
}
public int getDefaultNotificationsMarginBottom() {
- return mStackScrollerMargin;
+ return ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
}
public void setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index dc6efba..c466a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -73,6 +73,10 @@
public abstract class PanelViewController {
public static final boolean DEBUG = PanelView.DEBUG;
public static final String TAG = PanelView.class.getSimpleName();
+ public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
+ public static final float FLING_SPEED_UP_FACTOR = 0.6f;
+ public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f;
+ public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f;
private static final int NO_FIXED_DURATION = -1;
private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
@@ -269,13 +273,13 @@
mNotificationShadeWindowController = notificationShadeWindowController;
mFlingAnimationUtils = flingAnimationUtilsBuilder
.reset()
- .setMaxLengthSeconds(0.6f)
- .setSpeedUpFactor(0.6f)
+ .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
.build();
mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder
.reset()
- .setMaxLengthSeconds(0.5f)
- .setSpeedUpFactor(0.6f)
+ .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR)
.build();
mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder
.reset()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 1cb19ab..d6bf5f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -49,29 +49,29 @@
}
override fun onViewAttached() {
- moveFromCenterAnimationController?.let { animationController ->
- val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
- val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
+ if (moveFromCenterAnimationController == null) return
- val viewsToAnimate = arrayOf(
- statusBarLeftSide,
- systemIconArea
- )
+ val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
+ val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
- mView.viewTreeObserver.addOnPreDrawListener(object :
- ViewTreeObserver.OnPreDrawListener {
- override fun onPreDraw(): Boolean {
- animationController.onViewsReady(viewsToAnimate)
- mView.viewTreeObserver.removeOnPreDrawListener(this)
- return true
- }
- })
+ val viewsToAnimate = arrayOf(
+ statusBarLeftSide,
+ systemIconArea
+ )
- mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ ->
- val widthChanged = right - left != oldRight - oldLeft
- if (widthChanged) {
- moveFromCenterAnimationController.onStatusBarWidthChanged()
- }
+ mView.viewTreeObserver.addOnPreDrawListener(object :
+ ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ moveFromCenterAnimationController.onViewsReady(viewsToAnimate)
+ mView.viewTreeObserver.removeOnPreDrawListener(this)
+ return true
+ }
+ })
+
+ mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ ->
+ val widthChanged = right - left != oldRight - oldLeft
+ if (widthChanged) {
+ moveFromCenterAnimationController.onStatusBarWidthChanged()
}
}
@@ -162,9 +162,7 @@
PhoneStatusBarViewController(
view,
progressProvider.getOrNull(),
- unfoldComponent.map {
- it.getStatusBarMoveFromCenterAnimationController()
- }.getOrNull(),
+ unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
touchEventHandler,
configurationController
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
index 091831f..ea61a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -113,14 +113,16 @@
* the animation ends
*/
fun shouldDelayKeyguardShow(): Boolean =
- animations.any { it.shouldPlayAnimation() }
+ animations.any { it.shouldDelayKeyguardShow() }
/**
* Return true while we want to ignore requests to show keyguard, we need to handle pending
* keyguard lock requests manually
+ *
+ * @see [com.android.systemui.keyguard.KeyguardViewMediator.maybeHandlePendingLock]
*/
fun isKeyguardShowDelayed(): Boolean =
- animations.any { it.isAnimationPlaying() }
+ animations.any { it.isKeyguardShowDelayed() }
/**
* Return true to ignore requests to hide keyguard
@@ -211,6 +213,8 @@
fun shouldAnimateInKeyguard(): Boolean = false
fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run()
+ fun shouldDelayKeyguardShow(): Boolean = false
+ fun isKeyguardShowDelayed(): Boolean = false
fun isKeyguardHideDelayed(): Boolean = false
fun shouldHideScrimOnWakeUp(): Boolean = false
fun overrideNotificationsDozeAmount(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index d2e1650..ef5f216 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -62,7 +62,7 @@
public void prepare(ScrimState previousState) {
mBlankScreen = false;
if (previousState == ScrimState.AOD) {
- mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
+ mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP_SCRIM;
if (mDisplayRequiresBlanking) {
// DisplayPowerManager will blank the screen, we'll just
// set our scrim to black in this frame to avoid flickering and
@@ -70,7 +70,7 @@
mBlankScreen = true;
}
} else if (previousState == ScrimState.KEYGUARD) {
- mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
+ mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP_SCRIM;
} else {
mAnimationDuration = ScrimController.ANIMATION_DURATION;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index c09c3ca..c8cc807 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -150,6 +150,7 @@
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.emergency.EmergencyGesture;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -338,6 +339,7 @@
}
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ private final DreamOverlayStateController mDreamOverlayStateController;
private StatusBarCommandQueueCallbacks mCommandQueueCallbacks;
void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
@@ -781,7 +783,8 @@
ActivityLaunchAnimator activityLaunchAnimator,
NotifPipelineFlags notifPipelineFlags,
InteractionJankMonitor jankMonitor,
- DeviceStateManager deviceStateManager) {
+ DeviceStateManager deviceStateManager,
+ DreamOverlayStateController dreamOverlayStateController) {
super(context);
mNotificationsController = notificationsController;
mFragmentService = fragmentService;
@@ -869,6 +872,7 @@
mMessageRouter = messageRouter;
mWallpaperManager = wallpaperManager;
mJankMonitor = jankMonitor;
+ mDreamOverlayStateController = dreamOverlayStateController;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -2931,14 +2935,6 @@
return updateIsKeyguard();
}
- /**
- * stop(tag)
- * @return True if StatusBar state is FULLSCREEN_USER_SWITCHER.
- */
- public boolean isFullScreenUserSwitcherState() {
- return mState == StatusBarState.FULLSCREEN_USER_SWITCHER;
- }
-
boolean updateIsKeyguard() {
return updateIsKeyguard(false /* forceStateChange */);
}
@@ -2983,6 +2979,7 @@
}
public void showKeyguardImpl() {
+ Trace.beginSection("StatusBar#showKeyguard");
mIsKeyguard = true;
// In case we're locking while a smartspace transition is in progress, reset it.
mKeyguardUnlockAnimationController.resetSmartspaceTransition();
@@ -2991,20 +2988,17 @@
onLaunchTransitionFadingEnded();
}
mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
- if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) {
- mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER);
- } else if (!mLockscreenShadeTransitionController.isWakingToShadeLocked()) {
+ if (!mLockscreenShadeTransitionController.isWakingToShadeLocked()) {
mStatusBarStateController.setState(StatusBarState.KEYGUARD);
}
updatePanelExpansionForKeyguard();
+ Trace.endSection();
}
private void updatePanelExpansionForKeyguard() {
if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
!= BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
mShadeController.instantExpandNotificationsPanel();
- } else if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
- instantCollapseNotificationPanel();
}
}
@@ -4144,6 +4138,10 @@
return isBouncerShowing() && mStatusBarKeyguardViewManager.bouncerNeedsScrimming();
}
+ public boolean isBouncerShowingOverDream() {
+ return isBouncerShowing() && mDreamOverlayStateController.isOverlayActive();
+ }
+
/**
* When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 316e682..d42a423 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -29,6 +29,7 @@
import android.hardware.biometrics.BiometricSourceType;
import android.os.Bundle;
import android.os.SystemClock;
+import android.os.Trace;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -48,9 +49,9 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.DejankUtils;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -111,6 +112,7 @@
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final KeyguardBouncer.Factory mKeyguardBouncerFactory;
private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
+ private final DreamOverlayStateController mDreamOverlayStateController;
private KeyguardMessageAreaController mKeyguardMessageAreaController;
private final Lazy<ShadeController> mShadeController;
private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@@ -211,6 +213,7 @@
private final SysuiStatusBarStateController mStatusBarStateController;
private final DockManager mDockManager;
private final KeyguardUpdateMonitor mKeyguardUpdateManager;
+ private final LatencyTracker mLatencyTracker;
private KeyguardBypassController mBypassController;
@Nullable private AlternateAuthInterceptor mAlternateAuthInterceptor;
@@ -235,6 +238,7 @@
SysuiStatusBarStateController sysuiStatusBarStateController,
ConfigurationController configurationController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
+ DreamOverlayStateController dreamOverlayStateController,
NavigationModeController navigationModeController,
DockManager dockManager,
NotificationShadeWindowController notificationShadeWindowController,
@@ -242,13 +246,15 @@
NotificationMediaManager notificationMediaManager,
KeyguardBouncer.Factory keyguardBouncerFactory,
KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
- Lazy<ShadeController> shadeController) {
+ Lazy<ShadeController> shadeController,
+ LatencyTracker latencyTracker) {
mContext = context;
mViewMediatorCallback = callback;
mLockPatternUtils = lockPatternUtils;
mConfigurationController = configurationController;
mNavigationModeController = navigationModeController;
mNotificationShadeWindowController = notificationShadeWindowController;
+ mDreamOverlayStateController = dreamOverlayStateController;
mKeyguardStateController = keyguardStateController;
mMediaManager = notificationMediaManager;
mKeyguardUpdateManager = keyguardUpdateMonitor;
@@ -257,6 +263,7 @@
mKeyguardBouncerFactory = keyguardBouncerFactory;
mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
mShadeController = shadeController;
+ mLatencyTracker = latencyTracker;
}
@Override
@@ -370,6 +377,7 @@
*/
@Override
public void show(Bundle options) {
+ Trace.beginSection("StatusBarKeyguardViewManager#show");
mShowing = true;
mNotificationShadeWindowController.setKeyguardShowing(true);
mKeyguardStateController.notifyKeyguardState(mShowing,
@@ -377,6 +385,7 @@
reset(true /* hideBouncerWhenShowing */);
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
+ Trace.endSection();
}
/**
@@ -711,6 +720,7 @@
@Override
public void hide(long startTime, long fadeoutDuration) {
+ Trace.beginSection("StatusBarKeyguardViewManager#hide");
mShowing = false;
mKeyguardStateController.notifyKeyguardState(mShowing,
mKeyguardStateController.isOccluded());
@@ -810,6 +820,7 @@
}
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN);
+ Trace.endSection();
}
private boolean needsBypassFading() {
@@ -850,15 +861,11 @@
}
private void wakeAndUnlockDejank() {
- if (mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
- && LatencyTracker.isEnabled(mContext)) {
+ if (mBiometricUnlockController.isWakeAndUnlock() && mLatencyTracker.isEnabled()) {
BiometricSourceType type = mBiometricUnlockController.getBiometricType();
- DejankUtils.postAfterTraversal(() -> {
- LatencyTracker.getInstance(mContext).onActionEnd(
- type == BiometricSourceType.FACE
- ? LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK
- : LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK);
- });
+ mLatencyTracker.onActionEnd(type == BiometricSourceType.FACE
+ ? LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK
+ : LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK);
}
}
@@ -1174,8 +1181,9 @@
}
public boolean bouncerNeedsScrimming() {
- return mOccluded || mBouncer.willDismissWithAction()
- || mStatusBar.isFullScreenUserSwitcherState()
+ // When a dream overlay is active, scrimming will cause any expansion to immediately expand.
+ return (mOccluded && !mDreamOverlayStateController.isOverlayActive())
+ || mBouncer.willDismissWithAction()
|| (mBouncer.isShowing() && mBouncer.isScrimmed())
|| mBouncer.isFullscreenBouncer();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
index 6d033477..79c0984 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
@@ -18,11 +18,13 @@
import android.view.View
import android.view.WindowManager
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.AlphaProvider
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController.StatusBarViewsCenterProvider
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import javax.inject.Inject
+import kotlin.math.max
@SysUIUnfoldScope
class StatusBarMoveFromCenterAnimationController @Inject constructor(
@@ -31,8 +33,11 @@
) {
private val transitionListener = TransitionListener()
- private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager,
- viewCenterProvider = StatusBarViewsCenterProvider())
+ private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(
+ windowManager,
+ viewCenterProvider = StatusBarViewsCenterProvider(),
+ alphaProvider = StatusBarIconsAlphaProvider()
+ )
fun onViewsReady(viewsToAnimate: Array<View>) {
moveFromCenterAnimator.updateDisplayProperties()
@@ -65,4 +70,15 @@
moveFromCenterAnimator.onTransitionProgress(1f)
}
}
+
+ private class StatusBarIconsAlphaProvider : AlphaProvider {
+ override fun getAlpha(progress: Float): Float {
+ return max(
+ 0f,
+ (progress - ICONS_START_APPEARING_PROGRESS) / (1 - ICONS_START_APPEARING_PROGRESS)
+ )
+ }
+ }
}
+
+private const val ICONS_START_APPEARING_PROGRESS = 0.75F
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index e3b4caa..d6fc0a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -23,6 +23,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -87,11 +89,8 @@
this(context, theme, dismissOnDeviceLock, null);
}
- /**
- * @param udfpsDialogManager If set, UDFPS will hide if this dialog is showing.
- */
public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
- SystemUIDialogManager dialogManager) {
+ @Nullable SystemUIDialogManager dialogManager) {
super(context, theme);
mContext = context;
@@ -148,7 +147,7 @@
* the device configuration changes, and the result will be used to resize this dialog window.
*/
protected int getWidth() {
- return getDefaultDialogWidth(mContext);
+ return getDefaultDialogWidth(this);
}
/**
@@ -279,36 +278,53 @@
// We need to create the dialog first, otherwise the size will be overridden when it is
// created.
dialog.create();
- dialog.getWindow().setLayout(getDefaultDialogWidth(dialog.getContext()),
- getDefaultDialogHeight());
+ dialog.getWindow().setLayout(getDefaultDialogWidth(dialog), getDefaultDialogHeight());
}
- private static int getDefaultDialogWidth(Context context) {
- boolean isOnTablet = context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
- if (!isOnTablet) {
- return ViewGroup.LayoutParams.MATCH_PARENT;
- }
-
+ private static int getDefaultDialogWidth(Dialog dialog) {
+ Context context = dialog.getContext();
int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
if (flagValue == -1) {
// The width of bottom sheets (624dp).
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624,
- context.getResources().getDisplayMetrics()));
+ return calculateDialogWidthWithInsets(dialog, 624);
} else if (flagValue == -2) {
// The suggested small width for all dialogs (348dp)
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348,
- context.getResources().getDisplayMetrics()));
+ return calculateDialogWidthWithInsets(dialog, 348);
} else if (flagValue > 0) {
// Any given width.
- return Math.round(
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue,
- context.getResources().getDisplayMetrics()));
+ return calculateDialogWidthWithInsets(dialog, flagValue);
} else {
- // By default we use the same width as the notification shade in portrait mode (504dp).
- return context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
+ // By default we use the same width as the notification shade in portrait mode.
+ int width = context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
+ if (width > 0) {
+ // If we are neither WRAP_CONTENT or MATCH_PARENT, add the background insets so that
+ // the dialog is the desired width.
+ width += getHorizontalInsets(dialog);
+ }
+ return width;
}
}
+ /**
+ * Return the pixel width {@param dialog} should be so that it is {@param widthInDp} wide,
+ * taking its background insets into consideration.
+ */
+ private static int calculateDialogWidthWithInsets(Dialog dialog, int widthInDp) {
+ float widthInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, widthInDp,
+ dialog.getContext().getResources().getDisplayMetrics());
+ return Math.round(widthInPixels + getHorizontalInsets(dialog));
+ }
+
+ private static int getHorizontalInsets(Dialog dialog) {
+ if (dialog.getWindow().getDecorView() == null) {
+ return 0;
+ }
+
+ Drawable background = dialog.getWindow().getDecorView().getBackground();
+ Insets insets = background != null ? background.getOpticalInsets() : Insets.NONE;
+ return insets.left + insets.right;
+ }
+
private static int getDefaultDialogHeight() {
return ViewGroup.LayoutParams.WRAP_CONTENT;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index cc65ca02..0abe8e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -136,6 +136,12 @@
globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
}
+ override fun shouldDelayKeyguardShow(): Boolean =
+ shouldPlayAnimation()
+
+ override fun isKeyguardShowDelayed(): Boolean =
+ isAnimationPlaying()
+
/**
* Animates in the provided keyguard view, ending in the same position that it will be in on
* AOD.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index f5364b9..d3ff4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -40,6 +40,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -231,7 +232,8 @@
ActivityLaunchAnimator activityLaunchAnimator,
NotifPipelineFlags notifPipelineFlags,
InteractionJankMonitor jankMonitor,
- DeviceStateManager deviceStateManager) {
+ DeviceStateManager deviceStateManager,
+ DreamOverlayStateController dreamOverlayStateController) {
return new StatusBar(
context,
notificationsController,
@@ -327,7 +329,8 @@
activityLaunchAnimator,
notifPipelineFlags,
jankMonitor,
- deviceStateManager
+ deviceStateManager,
+ dreamOverlayStateController
);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 48949f92..3205e09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -118,6 +118,7 @@
private boolean mColorized;
private int mTint;
private boolean mResetting;
+ private boolean mWasSpinning;
// TODO(b/193539698): move these to a Controller
private RemoteInputController mController;
@@ -439,6 +440,10 @@
mEditText.requestFocus();
}
}
+ if (mWasSpinning) {
+ mController.addSpinning(mEntry.getKey(), mToken);
+ mWasSpinning = false;
+ }
}
@Override
@@ -447,6 +452,7 @@
mEditText.removeTextChangedListener(mTextWatcher);
mEditText.setOnEditorActionListener(null);
mEditText.mRemoteInputView = null;
+ mWasSpinning = mController.isSpinning(mEntry.getKey(), mToken);
if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {
return;
}
@@ -533,6 +539,8 @@
if (isActive() && mWrapper != null) {
mWrapper.setRemoteInputVisible(true);
}
+
+ mWasSpinning = false;
}
private void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 9f20bc5..1b73595 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -19,7 +19,6 @@
import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-import static com.android.systemui.DejankUtils.whitelistIpcs;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -427,22 +426,6 @@
return mSimpleUserSwitcher;
}
- public boolean useFullscreenUserSwitcher() {
- // Use adb to override:
- // adb shell settings put system enable_fullscreen_user_switcher 0 # Turn it off.
- // adb shell settings put system enable_fullscreen_user_switcher 1 # Turn it on.
- // Restart SystemUI or adb reboot.
- final int DEFAULT = -1;
- final int overrideUseFullscreenUserSwitcher =
- whitelistIpcs(() -> Settings.System.getInt(mContext.getContentResolver(),
- "enable_fullscreen_user_switcher", DEFAULT));
- if (overrideUseFullscreenUserSwitcher != DEFAULT) {
- return overrideUseFullscreenUserSwitcher != 0;
- }
- // Otherwise default to the build setting.
- return mContext.getResources().getBoolean(R.bool.config_enableFullscreenUserSwitcher);
- }
-
public void setResumeUserOnGuestLogout(boolean resume) {
mResumeUserOnGuestLogout = resume;
}
@@ -455,6 +438,13 @@
}
}
+ /**
+ * Returns whether the current user is a system user.
+ */
+ public boolean isSystemUser() {
+ return mUserTracker.getUserId() == UserHandle.USER_SYSTEM;
+ }
+
public void removeUserId(int userId) {
if (userId == UserHandle.USER_SYSTEM) {
Log.w(TAG, "User " + userId + " could not removed.");
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index c481fc9..2e627a8 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -20,7 +20,6 @@
import android.os.PowerManager
import android.provider.Settings
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.phone.ScreenOffAnimation
@@ -28,7 +27,6 @@
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
import com.android.systemui.util.settings.GlobalSettings
-import dagger.Lazy
import javax.inject.Inject
/**
@@ -40,7 +38,6 @@
@Inject
constructor(
@Main private val handler: Handler,
- private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
private val wakefulnessLifecycle: WakefulnessLifecycle,
private val globalSettings: GlobalSettings
) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
@@ -57,7 +54,6 @@
statusBar.notificationPanelViewController.startFoldToAodAnimation {
// End action
isAnimationPlaying = false
- keyguardViewMediatorLazy.get().maybeHandlePendingLock()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 07f9c54..7350b37 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -18,6 +18,7 @@
import com.android.keyguard.KeyguardUnfoldTransition
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.NotificationPanelUnfoldAnimationController
import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
@@ -85,6 +86,8 @@
fun getStatusBarMoveFromCenterAnimationController(): StatusBarMoveFromCenterAnimationController
+ fun getNotificationPanelUnfoldAnimationController(): NotificationPanelUnfoldAnimationController
+
fun getFoldAodAnimationController(): FoldAodAnimationController
fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
new file mode 100644
index 0000000..7687432
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
@@ -0,0 +1,45 @@
+/*
+ * 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.util.service;
+
+/**
+ * The {@link Observer} interface specifies an entity which listeners
+ * can be informed of changes to the source, which will require updating. Note that this deals
+ * with changes to the source itself, not content which will be updated through the interface.
+ */
+public interface Observer {
+ /**
+ * Callback for receiving updates from the {@link Observer}.
+ */
+ interface Callback {
+ /**
+ * Invoked when the source has changed.
+ */
+ void onSourceChanged();
+ }
+
+ /**
+ * Adds a callback to receive future updates from the {@link Observer}.
+ */
+ void addCallback(Callback callback);
+
+ /**
+ * Removes a callback from receiving further updates.
+ * @param callback
+ */
+ void removeCallback(Callback callback);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java b/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java
new file mode 100644
index 0000000..2ee7b20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java
@@ -0,0 +1,107 @@
+/*
+ * 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.util.service;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PatternMatcher;
+import android.util.Log;
+
+import com.android.systemui.communal.CommunalSource;
+
+import com.google.android.collect.Lists;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.inject.Inject;
+
+/**
+ * {@link PackageObserver} allows for monitoring the system for changes relating to a particular
+ * package. This can be used by {@link CommunalSource} clients to detect when a related package
+ * has changed and reloading is necessary.
+ */
+public class PackageObserver implements Observer {
+ private static final String TAG = "PackageObserver";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList();
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Log.d(TAG, "package added receiver - onReceive");
+ }
+
+ final Iterator<WeakReference<Callback>> iter = mCallbacks.iterator();
+ while (iter.hasNext()) {
+ final Callback callback = iter.next().get();
+ if (callback != null) {
+ callback.onSourceChanged();
+ } else {
+ iter.remove();
+ }
+ }
+ }
+ };
+
+ private final String mPackageName;
+ private final Context mContext;
+
+ @Inject
+ public PackageObserver(Context context, ComponentName component) {
+ mContext = context;
+ mPackageName = component.getPackageName();
+ }
+
+ @Override
+ public void addCallback(Callback callback) {
+ if (DEBUG) {
+ Log.d(TAG, "addCallback:" + callback);
+ }
+ mCallbacks.add(new WeakReference<>(callback));
+
+ // Only register for listening to package additions on first callback.
+ if (mCallbacks.size() > 1) {
+ return;
+ }
+
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addDataScheme("package");
+ filter.addDataSchemeSpecificPart(mPackageName, PatternMatcher.PATTERN_LITERAL);
+ // Note that we directly register the receiver here as data schemes are not supported by
+ // BroadcastDispatcher.
+ mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
+ }
+
+ @Override
+ public void removeCallback(Callback callback) {
+ if (DEBUG) {
+ Log.d(TAG, "removeCallback:" + callback);
+ }
+ final boolean removed = mCallbacks.removeIf(el -> el.get() == callback);
+
+ if (removed && mCallbacks.isEmpty()) {
+ mContext.unregisterReceiver(mReceiver);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
new file mode 100644
index 0000000..292c062
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -0,0 +1,155 @@
+/*
+ * 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.util.service;
+
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.BASE_RECONNECT_DELAY_MS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.MAX_RECONNECT_ATTEMPTS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.MIN_CONNECTION_DURATION_MS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.OBSERVER;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.SERVICE_CONNECTION;
+
+import android.util.Log;
+
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.SystemClock;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * The {@link PersistentConnectionManager} is responsible for maintaining a connection to a
+ * {@link ObservableServiceConnection}.
+ * @param <T> The transformed connection type handled by the service.
+ */
+public class PersistentConnectionManager<T> {
+ private static final String TAG = "PersistentConnManager";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final SystemClock mSystemClock;
+ private final DelayableExecutor mMainExecutor;
+ private final int mBaseReconnectDelayMs;
+ private final int mMaxReconnectAttempts;
+ private final int mMinConnectionDuration;
+ private final Observer mObserver;
+
+ private int mReconnectAttempts = 0;
+ private Runnable mCurrentReconnectCancelable;
+
+ private final ObservableServiceConnection<T> mConnection;
+
+ private final Runnable mConnectRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mCurrentReconnectCancelable = null;
+ mConnection.bind();
+ }
+ };
+
+ private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
+
+ private final ObservableServiceConnection.Callback mConnectionCallback =
+ new ObservableServiceConnection.Callback() {
+ private long mStartTime;
+
+ @Override
+ public void onConnected(ObservableServiceConnection connection, Object proxy) {
+ mStartTime = mSystemClock.currentTimeMillis();
+ }
+
+ @Override
+ public void onDisconnected(ObservableServiceConnection connection, int reason) {
+ if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) {
+ initiateConnectionAttempt();
+ } else {
+ scheduleConnectionAttempt();
+ }
+ }
+ };
+
+ @Inject
+ public PersistentConnectionManager(
+ SystemClock clock,
+ DelayableExecutor mainExecutor,
+ @Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
+ @Named(MAX_RECONNECT_ATTEMPTS) int maxReconnectAttempts,
+ @Named(BASE_RECONNECT_DELAY_MS) int baseReconnectDelayMs,
+ @Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs,
+ @Named(OBSERVER) Observer observer) {
+ mSystemClock = clock;
+ mMainExecutor = mainExecutor;
+ mConnection = serviceConnection;
+ mObserver = observer;
+
+ mMaxReconnectAttempts = maxReconnectAttempts;
+ mBaseReconnectDelayMs = baseReconnectDelayMs;
+ mMinConnectionDuration = minConnectionDurationMs;
+ }
+
+ /**
+ * Begins the {@link PersistentConnectionManager} by connecting to the associated service.
+ */
+ public void start() {
+ mConnection.addCallback(mConnectionCallback);
+ mObserver.addCallback(mObserverCallback);
+ initiateConnectionAttempt();
+ }
+
+ /**
+ * Brings down the {@link PersistentConnectionManager}, disconnecting from the service.
+ */
+ public void stop() {
+ mConnection.removeCallback(mConnectionCallback);
+ mObserver.removeCallback(mObserverCallback);
+ mConnection.unbind();
+ }
+
+ private void initiateConnectionAttempt() {
+ // Reset attempts
+ mReconnectAttempts = 0;
+
+ // The first attempt is always a direct invocation rather than delayed.
+ mConnection.bind();
+ }
+
+ private void scheduleConnectionAttempt() {
+ // always clear cancelable if present.
+ if (mCurrentReconnectCancelable != null) {
+ mCurrentReconnectCancelable.run();
+ mCurrentReconnectCancelable = null;
+ }
+
+ if (mReconnectAttempts >= mMaxReconnectAttempts) {
+ if (DEBUG) {
+ Log.d(TAG, "exceeded max connection attempts.");
+ }
+ return;
+ }
+
+ final long reconnectDelayMs =
+ (long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
+
+ if (DEBUG) {
+ Log.d(TAG,
+ "scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
+ }
+
+ mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
+ reconnectDelayMs);
+
+ mReconnectAttempts++;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
new file mode 100644
index 0000000..c62c957
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
@@ -0,0 +1,65 @@
+/*
+ * 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.util.service.dagger;
+
+import android.content.res.Resources;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import javax.inject.Named;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Module containing components and parameters for
+ * {@link com.android.systemui.util.service.ObservableServiceConnection}
+ * and {@link com.android.systemui.util.service.PersistentConnectionManager}.
+ */
+@Module(subcomponents = {
+ PackageObserverComponent.class,
+})
+public class ObservableServiceModule {
+ public static final String MAX_RECONNECT_ATTEMPTS = "max_reconnect_attempts";
+ public static final String BASE_RECONNECT_DELAY_MS = "base_reconnect_attempts";
+ public static final String MIN_CONNECTION_DURATION_MS = "min_connection_duration_ms";
+ public static final String SERVICE_CONNECTION = "service_connection";
+ public static final String OBSERVER = "observer";
+
+ @Provides
+ @Named(MAX_RECONNECT_ATTEMPTS)
+ static int providesMaxReconnectAttempts(@Main Resources resources) {
+ return resources.getInteger(
+ R.integer.config_communalSourceMaxReconnectAttempts);
+ }
+
+ @Provides
+ @Named(BASE_RECONNECT_DELAY_MS)
+ static int provideBaseReconnectDelayMs(@Main Resources resources) {
+ return resources.getInteger(
+ R.integer.config_communalSourceReconnectBaseDelay);
+ }
+
+ @Provides
+ @Named(MIN_CONNECTION_DURATION_MS)
+ static int providesMinConnectionDuration(@Main Resources resources) {
+ return resources.getInteger(
+ R.integer.config_connectionMinDuration);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java b/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java
new file mode 100644
index 0000000..8ee39b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java
@@ -0,0 +1,42 @@
+/*
+ * 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.util.service.dagger;
+
+import android.content.ComponentName;
+
+import com.android.systemui.util.service.PackageObserver;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Generates a scoped {@link PackageObserver}.
+ */
+@Subcomponent
+public interface PackageObserverComponent {
+ /**
+ * Generates a {@link PackageObserverComponent} instance.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ PackageObserverComponent create(@BindsInstance ComponentName component);
+ }
+
+ /**
+ * Creates a {@link PackageObserver}.
+ */
+ PackageObserver getPackageObserver();
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
index 164f83d..6c1f008 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
@@ -20,23 +20,21 @@
import android.view.View
import android.view.ViewGroup
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUnfoldTransition.Companion.LEFT
-import com.android.keyguard.KeyguardUnfoldTransition.Companion.RIGHT
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import com.android.systemui.util.mockito.capture
-import org.junit.Assert.assertEquals
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
/**
* Translates items away/towards the hinge when the device is opened/closed. This is controlled by
@@ -46,14 +44,11 @@
@RunWith(AndroidTestingRunner::class)
class KeyguardUnfoldTransitionTest : SysuiTestCase() {
- @Mock
- private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
+ @Mock private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
- @Captor
- private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
+ @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
- @Mock
- private lateinit var parent: ViewGroup
+ @Mock private lateinit var parent: ViewGroup
private lateinit var keyguardUnfoldTransition: KeyguardUnfoldTransition
private lateinit var progressListener: TransitionProgressListener
@@ -63,87 +58,35 @@
fun setup() {
MockitoAnnotations.initMocks(this)
- xTranslationMax = context.resources.getDimensionPixelSize(
- R.dimen.keyguard_unfold_translation_x).toFloat()
+ xTranslationMax =
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat()
- keyguardUnfoldTransition = KeyguardUnfoldTransition(
- getContext(),
- progressProvider
- )
-
- verify(progressProvider).addCallback(capture(progressListenerCaptor))
- progressListener = progressListenerCaptor.value
+ keyguardUnfoldTransition = KeyguardUnfoldTransition(context, progressProvider)
keyguardUnfoldTransition.setup(parent)
keyguardUnfoldTransition.statusViewCentered = false
- }
- @Test
- fun onTransition_noMatchingIds() {
- // GIVEN no views matching any ids
- // WHEN the transition starts
- progressListener.onTransitionStarted()
- progressListener.onTransitionProgress(.1f)
-
- // THEN nothing... no exceptions
- }
-
- @Test
- fun onTransition_oneMovesLeft() {
- // GIVEN one view with a matching id
- val view = View(getContext())
- `when`(parent.findViewById<View>(R.id.keyguard_status_area)).thenReturn(view)
-
- moveAndValidate(listOf(view to LEFT))
- }
-
- @Test
- fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() {
- // GIVEN two views with a matching id
- val leftView = View(getContext())
- val rightView = View(getContext())
- `when`(parent.findViewById<View>(R.id.keyguard_status_area)).thenReturn(leftView)
- `when`(parent.findViewById<View>(R.id.notification_stack_scroller)).thenReturn(rightView)
-
- moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
- moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+ verify(progressProvider).addCallback(capture(progressListenerCaptor))
+ progressListener = progressListenerCaptor.value
}
@Test
fun onTransition_centeredViewDoesNotMove() {
keyguardUnfoldTransition.statusViewCentered = true
- val view = View(getContext())
+ val view = View(context)
`when`(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
- moveAndValidate(listOf(view to 0))
- }
-
- private fun moveAndValidate(list: List<Pair<View, Int>>) {
- // Compare values as ints because -0f != 0f
-
- // WHEN the transition starts
progressListener.onTransitionStarted()
+ assertThat(view.translationX).isZero()
+
progressListener.onTransitionProgress(0f)
+ assertThat(view.translationX).isZero()
- list.forEach { (view, direction) ->
- assertEquals((-xTranslationMax * direction).toInt(), view.getTranslationX().toInt())
- }
+ progressListener.onTransitionProgress(0.5f)
+ assertThat(view.translationX).isZero()
- // WHEN the transition progresses, translation is updated
- progressListener.onTransitionProgress(.5f)
- list.forEach { (view, direction) ->
- assertEquals(
- (-xTranslationMax / 2f * direction).toInt(),
- view.getTranslationX().toInt()
- )
- }
-
- // WHEN the transition ends, translation is completed
- progressListener.onTransitionProgress(1f)
progressListener.onTransitionFinished()
- list.forEach { (view, _) ->
- assertEquals(0, view.getTranslationX().toInt())
- }
+ assertThat(view.translationX).isZero()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 5d39eef..c37e966 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -27,10 +27,12 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -46,6 +48,7 @@
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorProperties;
@@ -70,6 +73,7 @@
import com.android.internal.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.concurrency.FakeExecution;
@@ -80,6 +84,7 @@
import org.mockito.AdditionalMatchers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -99,6 +104,8 @@
@Mock
private IBiometricSysuiReceiver mReceiver;
@Mock
+ private IBiometricContextListener mContextListener;
+ @Mock
private AuthDialog mDialog1;
@Mock
private AuthDialog mDialog2;
@@ -120,10 +127,14 @@
private DisplayManager mDisplayManager;
@Mock
private WakefulnessLifecycle mWakefulnessLifecycle;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
@Captor
ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
@Captor
ArgumentCaptor<FingerprintStateListener> mFingerprintStateCaptor;
+ @Captor
+ ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
private TestableContext mContextSpy;
private Execution mExecution;
@@ -175,12 +186,15 @@
mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
- () -> mUdfpsController, () -> mSidefpsController);
+ () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController);
mAuthController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
mAuthenticatorsRegisteredCaptor.capture());
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
+
mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(props);
// Ensures that the operations posted on the handler get executed.
@@ -198,7 +212,8 @@
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
- mFaceManager, () -> mUdfpsController, () -> mSidefpsController);
+ mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+ mStatusBarStateController);
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -221,7 +236,8 @@
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
- mFaceManager, () -> mUdfpsController, () -> mSidefpsController);
+ mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+ mStatusBarStateController);
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -656,6 +672,19 @@
verify(callback).onBiometricPromptDismissed();
}
+ @Test
+ public void testForwardsDozeEvent() throws RemoteException {
+ mAuthController.setBiometicContextListener(mContextListener);
+
+ mStatusBarStateListenerCaptor.getValue().onDozingChanged(false);
+ mStatusBarStateListenerCaptor.getValue().onDozingChanged(true);
+
+ InOrder order = inOrder(mContextListener);
+ // invoked twice since the initial state is false
+ order.verify(mContextListener, times(2)).onDozeChanged(eq(false));
+ order.verify(mContextListener).onDozeChanged(eq(true));
+ }
+
// Helpers
private void showDialog(int[] sensorIds, boolean credentialAllowed) {
@@ -705,10 +734,12 @@
FingerprintManager fingerprintManager,
FaceManager faceManager,
Provider<UdfpsController> udfpsControllerFactory,
- Provider<SidefpsController> sidefpsControllerFactory) {
+ Provider<SidefpsController> sidefpsControllerFactory,
+ StatusBarStateController statusBarStateController) {
super(context, execution, commandQueue, activityTaskManager, windowManager,
fingerprintManager, faceManager, udfpsControllerFactory,
- sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle, mHandler);
+ sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle,
+ statusBarStateController, mHandler);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index b3b5fa5..d5bd67a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -91,6 +91,9 @@
@Mock
DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
+ @Mock
+ DreamOverlayStateController mStateController;
+
DreamOverlayService mService;
@@ -115,6 +118,7 @@
mService = new DreamOverlayService(mContext, mMainExecutor,
mDreamOverlayComponentFactory,
+ mStateController,
mKeyguardUpdateMonitor);
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 7d0833d..627da3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -16,11 +16,15 @@
package com.android.systemui.dreams;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -35,6 +39,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Collection;
@@ -56,7 +61,29 @@
}
@Test
- public void testCallback() throws Exception {
+ public void testStateChange() {
+ final DreamOverlayStateController stateController = new DreamOverlayStateController(
+ mExecutor);
+ stateController.addCallback(mCallback);
+ stateController.setOverlayActive(true);
+ mExecutor.runAllReady();
+
+ verify(mCallback).onStateChanged();
+ assertThat(stateController.isOverlayActive()).isTrue();
+
+ Mockito.clearInvocations(mCallback);
+ stateController.setOverlayActive(true);
+ mExecutor.runAllReady();
+ verify(mCallback, never()).onStateChanged();
+
+ stateController.setOverlayActive(false);
+ mExecutor.runAllReady();
+ verify(mCallback).onStateChanged();
+ assertThat(stateController.isOverlayActive()).isFalse();
+ }
+
+ @Test
+ public void testCallback() {
final DreamOverlayStateController stateController = new DreamOverlayStateController(
mExecutor);
stateController.addCallback(mCallback);
@@ -94,4 +121,43 @@
mExecutor.runAllReady();
verify(mCallback, times(1)).onComplicationsChanged();
}
+
+ @Test
+ public void testComplicationFiltering() {
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor);
+
+ final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
+ final Complication weatherComplication = Mockito.mock(Complication.class);
+ when(alwaysAvailableComplication.getRequiredTypeAvailability())
+ .thenReturn(Complication.COMPLICATION_TYPE_NONE);
+ when(weatherComplication.getRequiredTypeAvailability())
+ .thenReturn(Complication.COMPLICATION_TYPE_WEATHER);
+
+ stateController.addComplication(alwaysAvailableComplication);
+ stateController.addComplication(weatherComplication);
+
+ final DreamOverlayStateController.Callback callback =
+ Mockito.mock(DreamOverlayStateController.Callback.class);
+
+ stateController.addCallback(callback);
+ mExecutor.runAllReady();
+
+ {
+ final Collection<Complication> complications = stateController.getComplications();
+ assertThat(complications.contains(alwaysAvailableComplication)).isTrue();
+ assertThat(complications.contains(weatherComplication)).isFalse();
+ }
+
+ stateController.setAvailableComplicationTypes(Complication.COMPLICATION_TYPE_WEATHER);
+ mExecutor.runAllReady();
+ verify(callback).onAvailableComplicationTypesChanged();
+
+ {
+ final Collection<Complication> complications = stateController.getComplications();
+ assertThat(complications.contains(alwaysAvailableComplication)).isTrue();
+ assertThat(complications.contains(weatherComplication)).isTrue();
+ }
+
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
new file mode 100644
index 0000000..3b17a80
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.dreams;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class SmartSpaceComplicationTest extends SysuiTestCase {
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private LockscreenSmartspaceController mSmartspaceController;
+
+ @Mock
+ private DreamOverlayStateController mDreamOverlayStateController;
+
+ @Mock
+ private SmartSpaceComplication mComplication;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Ensures {@link SmartSpaceComplication} is only registered when it is available.
+ */
+ @Test
+ public void testAvailability() {
+ when(mSmartspaceController.isEnabled()).thenReturn(false);
+
+ final SmartSpaceComplication.Registrant registrant = new SmartSpaceComplication.Registrant(
+ mContext,
+ mDreamOverlayStateController,
+ mComplication,
+ mSmartspaceController);
+ registrant.start();
+ verify(mDreamOverlayStateController, never()).addComplication(any());
+
+ when(mSmartspaceController.isEnabled()).thenReturn(true);
+ registrant.start();
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
index feeea5d..5fcf414 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
@@ -75,6 +75,10 @@
callbackCaptor.getValue().onComplicationsChanged();
verifyUpdate(observer, complications);
+
+ callbackCaptor.getValue().onAvailableComplicationTypesChanged();
+
+ verifyUpdate(observer, complications);
});
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index f227a9b..d5ab708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -27,6 +27,7 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.test.filters.SmallTest;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
@@ -122,6 +123,34 @@
}
/**
+ * Makes sure the engine properly places a view within the {@link ConstraintLayout}.
+ */
+ @Test
+ public void testSnapToGuide() {
+ final ViewInfo firstViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0,
+ true),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+ addComplication(engine, firstViewInfo);
+
+ // Ensure the view is added to the top end corner
+ verifyChange(firstViewInfo, true, lp -> {
+ assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.startToEnd == R.id.complication_end_guide).isTrue();
+ });
+ }
+
+ /**
* Ensures layout in a particular direction updates.
*/
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
new file mode 100644
index 0000000..f1978b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.dreams.complication;
+
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
+import static com.android.systemui.dreams.complication.ComplicationUtils.convertComplicationType;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.dream.DreamBackend;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationUtilsTest extends SysuiTestCase {
+ @Test
+ public void testConvertComplicationType() {
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_TIME))
+ .isEqualTo(COMPLICATION_TYPE_TIME);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_DATE))
+ .isEqualTo(COMPLICATION_TYPE_DATE);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_WEATHER))
+ .isEqualTo(COMPLICATION_TYPE_WEATHER);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_AIR_QUALITY))
+ .isEqualTo(COMPLICATION_TYPE_AIR_QUALITY);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_CAST_INFO))
+ .isEqualTo(COMPLICATION_TYPE_CAST_INFO);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
new file mode 100644
index 0000000..b02c506
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.dreams.complication;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamClockDateComplicationTest extends SysuiTestCase {
+ @SuppressWarnings("HidingField")
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private DreamOverlayStateController mDreamOverlayStateController;
+
+ @Mock
+ private DreamClockDateComplication mComplication;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Ensures {@link DreamClockDateComplication} is registered.
+ */
+ @Test
+ public void testComplicationAdded() {
+ final DreamClockDateComplication.Registrant registrant =
+ new DreamClockDateComplication.Registrant(
+ mContext,
+ mDreamOverlayStateController,
+ mComplication);
+ registrant.start();
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
new file mode 100644
index 0000000..088b4d5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.dreams.complication;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamClockTimeComplicationTest extends SysuiTestCase {
+ @SuppressWarnings("HidingField")
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private DreamOverlayStateController mDreamOverlayStateController;
+
+ @Mock
+ private DreamClockTimeComplication mComplication;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Ensures {@link DreamClockTimeComplication} is registered.
+ */
+ @Test
+ public void testComplicationAdded() {
+ final DreamClockTimeComplication.Registrant registrant =
+ new DreamClockTimeComplication.Registrant(
+ mContext,
+ mDreamOverlayStateController,
+ mComplication);
+ registrant.start();
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java
new file mode 100644
index 0000000..151742a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.dreams.complication;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamWeatherComplicationTest extends SysuiTestCase {
+ @SuppressWarnings("HidingField")
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private LockscreenSmartspaceController mSmartspaceController;
+
+ @Mock
+ private DreamOverlayStateController mDreamOverlayStateController;
+
+ @Mock
+ private DreamWeatherComplication mComplication;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Ensures {@link DreamWeatherComplication} is only registered when it is available.
+ */
+ @Test
+ public void testComplicationAvailability() {
+ when(mSmartspaceController.isEnabled()).thenReturn(false);
+ final DreamWeatherComplication.Registrant registrant =
+ new DreamWeatherComplication.Registrant(
+ mContext,
+ mSmartspaceController,
+ mDreamOverlayStateController,
+ mComplication);
+ registrant.start();
+ verify(mDreamOverlayStateController, never()).addComplication(any());
+
+ when(mSmartspaceController.isEnabled()).thenReturn(true);
+ registrant.start();
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
new file mode 100644
index 0000000..1a8326f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -0,0 +1,252 @@
+/*
+ * 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.dreams.touch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.testing.AndroidTestingRunner;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Random;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
+ @Mock
+ StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+
+ @Mock
+ StatusBar mStatusBar;
+
+ @Mock
+ NotificationShadeWindowController mNotificationShadeWindowController;
+
+ @Mock
+ FlingAnimationUtils mFlingAnimationUtils;
+
+
+ @Mock
+ FlingAnimationUtils mFlingAnimationUtilsClosing;
+
+ @Mock
+ DreamTouchHandler.TouchSession mTouchSession;
+
+ BouncerSwipeTouchHandler mTouchHandler;
+
+ @Mock
+ BouncerSwipeTouchHandler.ValueAnimatorCreator mValueAnimatorCreator;
+
+ @Mock
+ ValueAnimator mValueAnimator;
+
+ @Mock
+ BouncerSwipeTouchHandler.VelocityTrackerFactory mVelocityTrackerFactory;
+
+ @Mock
+ VelocityTracker mVelocityTracker;
+
+ private static final float TOUCH_REGION = .3f;
+ private static final float SCREEN_HEIGHT_PX = 100;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTouchHandler = new BouncerSwipeTouchHandler(
+ mStatusBarKeyguardViewManager,
+ mStatusBar,
+ mNotificationShadeWindowController,
+ mValueAnimatorCreator,
+ mVelocityTrackerFactory,
+ mFlingAnimationUtils,
+ mFlingAnimationUtilsClosing,
+ TOUCH_REGION);
+ when(mStatusBar.getDisplayHeight()).thenReturn(SCREEN_HEIGHT_PX);
+ when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
+ when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
+ }
+
+ private static void beginValidSwipe(GestureDetector.OnGestureListener listener) {
+ listener.onDown(MotionEvent.obtain(0, 0,
+ MotionEvent.ACTION_DOWN, 0,
+ SCREEN_HEIGHT_PX - (.5f * TOUCH_REGION * SCREEN_HEIGHT_PX), 0));
+ }
+
+ /**
+ * Ensures expansion only happens when touch down happens in valid part of the screen.
+ */
+ @Test
+ public void testSessionStart() {
+ mTouchHandler.onSessionStart(mTouchSession);
+ verify(mNotificationShadeWindowController).setForcePluginOpen(eq(true), any());
+ ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(mTouchSession).registerInputListener(eventListenerCaptor.capture());
+
+ final Random random = new Random(System.currentTimeMillis());
+
+ // If an initial touch down meeting criteria has been met, scroll behavior should be
+ // ignored.
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ random.nextFloat(),
+ random.nextFloat())).isFalse();
+
+ // A touch at the top of the screen should also not trigger listening.
+ final MotionEvent touchDownEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN,
+ 0, 0, 0);
+
+ gestureListenerCaptor.getValue().onDown(touchDownEvent);
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ random.nextFloat(),
+ random.nextFloat())).isFalse();
+
+ // A touch within range at the bottom of the screen should trigger listening
+ beginValidSwipe(gestureListenerCaptor.getValue());
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ random.nextFloat(),
+ random.nextFloat())).isTrue();
+ }
+
+ /**
+ * Makes sure expansion amount is proportional to scroll.
+ */
+ @Test
+ public void testExpansionAmount() {
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+ beginValidSwipe(gestureListenerCaptor.getValue());
+
+ final float scrollAmount = .3f;
+ final float distanceY = SCREEN_HEIGHT_PX * scrollAmount;
+
+ final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+ 0, SCREEN_HEIGHT_PX, 0);
+ final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+ 0, SCREEN_HEIGHT_PX - distanceY, 0);
+
+ assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0 , distanceY))
+ .isTrue();
+
+ // Ensure only called once
+ verify(mStatusBarKeyguardViewManager)
+ .onPanelExpansionChanged(anyFloat(), anyBoolean(), anyBoolean());
+
+ // Ensure correct expansion passed in.
+ verify(mStatusBarKeyguardViewManager)
+ .onPanelExpansionChanged(eq(1 - scrollAmount), eq(false), eq(true));
+ }
+
+ private void swipeToPosition(float position, float velocityY) {
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+ ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(mTouchSession).registerInputListener(inputEventListenerCaptor.capture());
+
+ when(mVelocityTracker.getYVelocity()).thenReturn(velocityY);
+
+ beginValidSwipe(gestureListenerCaptor.getValue());
+
+ final float distanceY = SCREEN_HEIGHT_PX * position;
+
+ final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+ 0, SCREEN_HEIGHT_PX, 0);
+ final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+ 0, SCREEN_HEIGHT_PX - distanceY, 0);
+
+ assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0 , distanceY))
+ .isTrue();
+
+ final MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP,
+ 0, 0, 0);
+
+ inputEventListenerCaptor.getValue().onInputEvent(upEvent);
+ }
+
+ /**
+ * Tests that ending a swipe before the set expansion threshold leads to bouncer collapsing
+ * down.
+ */
+ @Test
+ public void testCollapseOnThreshold() {
+ final float swipeUpPercentage = .3f;
+ swipeToPosition(swipeUpPercentage, -1);
+
+ verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage),
+ eq(KeyguardBouncer.EXPANSION_VISIBLE));
+ verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator), anyFloat(), anyFloat(),
+ anyFloat(), anyFloat());
+ verify(mValueAnimator).start();
+ }
+
+ /**
+ * Tests that ending a swipe above the set expansion threshold will continue the expansion.
+ */
+ @Test
+ public void testExpandOnThreshold() {
+ final float swipeUpPercentage = .7f;
+ swipeToPosition(swipeUpPercentage, 1);
+
+ verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage),
+ eq(KeyguardBouncer.EXPANSION_HIDDEN));
+ verify(mFlingAnimationUtils).apply(eq(mValueAnimator), anyFloat(), anyFloat(),
+ anyFloat(), anyFloat());
+ verify(mValueAnimator).start();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java
new file mode 100644
index 0000000..1171bd2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.hdmi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Locale;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class HdmiCecSetMenuLanguageHelperTest extends SysuiTestCase {
+
+ private HdmiCecSetMenuLanguageHelper mHdmiCecSetMenuLanguageHelper;
+
+ @Mock
+ private Executor mExecutor;
+
+ @Mock
+ private SecureSettings mSecureSettings;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mSecureSettings.getString(
+ Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST)).thenReturn(null);
+ mHdmiCecSetMenuLanguageHelper =
+ new HdmiCecSetMenuLanguageHelper(mExecutor, mSecureSettings);
+ }
+
+ @Test
+ public void testSetGetLocale() {
+ mHdmiCecSetMenuLanguageHelper.setLocale("en");
+ assertThat(mHdmiCecSetMenuLanguageHelper.getLocale()).isEqualTo(Locale.ENGLISH);
+ }
+
+ @Test
+ public void testIsLocaleDenylisted_EmptyByDefault() {
+ mHdmiCecSetMenuLanguageHelper.setLocale("en");
+ assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(false);
+ }
+
+ @Test
+ public void testIsLocaleDenylisted_AcceptLanguage() {
+ mHdmiCecSetMenuLanguageHelper.setLocale("de");
+ mHdmiCecSetMenuLanguageHelper.acceptLocale();
+ assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(false);
+ verify(mExecutor).execute(any());
+ }
+
+ @Test
+ public void testIsLocaleDenylisted_DeclineLanguage() {
+ mHdmiCecSetMenuLanguageHelper.setLocale("de");
+ mHdmiCecSetMenuLanguageHelper.declineLocale();
+ assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
+ verify(mSecureSettings).putString(
+ Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de");
+ }
+
+ @Test
+ public void testIsLocaleDenylisted_DeclineTwoLanguages() {
+ mHdmiCecSetMenuLanguageHelper.setLocale("de");
+ mHdmiCecSetMenuLanguageHelper.declineLocale();
+ assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
+ verify(mSecureSettings).putString(
+ Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de");
+ mHdmiCecSetMenuLanguageHelper.setLocale("pl");
+ mHdmiCecSetMenuLanguageHelper.declineLocale();
+ assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
+ verify(mSecureSettings).putString(
+ Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de,pl");
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index da8ab27..d94e2ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -48,6 +48,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollectorFake;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -98,6 +99,7 @@
private @Mock InteractionJankMonitor mInteractionJankMonitor;
private @Mock ScreenOnCoordinator mScreenOnCoordinator;
private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
+ private @Mock DreamOverlayStateController mDreamOverlayStateController;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -202,6 +204,7 @@
() -> mNotificationShadeDepthController,
mScreenOnCoordinator,
mInteractionJankMonitor,
+ mDreamOverlayStateController,
mNotificationShadeWindowControllerLazy);
mViewMediator.start();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
index 43d9a75..dc7026d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
@@ -95,7 +95,6 @@
fun testVisibleOnKeyguardOrFullScreenUserSwitcher() {
testStateVisibility(StatusBarState.SHADE, GONE)
testStateVisibility(StatusBarState.SHADE_LOCKED, GONE)
- testStateVisibility(StatusBarState.FULLSCREEN_USER_SWITCHER, VISIBLE)
testStateVisibility(StatusBarState.KEYGUARD, VISIBLE)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 140a395..609291a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -125,8 +125,9 @@
private lateinit var settings: View
private lateinit var settingsText: TextView
private lateinit var cancel: View
+ private lateinit var cancelText: TextView
private lateinit var dismiss: FrameLayout
- private lateinit var dismissLabel: View
+ private lateinit var dismissText: TextView
private lateinit var session: MediaSession
private val device = MediaDeviceData(true, null, DEVICE_NAME)
@@ -163,8 +164,9 @@
settings = View(context)
settingsText = TextView(context)
cancel = View(context)
+ cancelText = TextView(context)
dismiss = FrameLayout(context)
- dismissLabel = View(context)
+ dismissText = TextView(context)
initPlayerHolderMocks()
initSessionHolderMocks()
@@ -244,13 +246,15 @@
whenever(holder.settings).thenReturn(settings)
whenever(holder.settingsText).thenReturn(settingsText)
whenever(holder.cancel).thenReturn(cancel)
+ whenever(holder.cancelText).thenReturn(cancelText)
whenever(holder.dismiss).thenReturn(dismiss)
- whenever(holder.dismissLabel).thenReturn(dismissLabel)
+ whenever(holder.dismissText).thenReturn(dismissText)
}
/** Mock view holder for session player */
private fun initSessionHolderMocks() {
whenever(sessionHolder.player).thenReturn(view)
+ whenever(sessionHolder.albumView).thenReturn(albumView)
whenever(sessionHolder.appIcon).thenReturn(appIcon)
whenever(sessionHolder.titleText).thenReturn(titleText)
whenever(sessionHolder.artistText).thenReturn(artistText)
@@ -284,8 +288,9 @@
whenever(sessionHolder.settings).thenReturn(settings)
whenever(sessionHolder.settingsText).thenReturn(settingsText)
whenever(sessionHolder.cancel).thenReturn(cancel)
+ whenever(sessionHolder.cancelText).thenReturn(cancelText)
whenever(sessionHolder.dismiss).thenReturn(dismiss)
- whenever(sessionHolder.dismissLabel).thenReturn(dismissLabel)
+ whenever(sessionHolder.dismissText).thenReturn(dismissText)
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index a3ffb2f..97b3b10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
+import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -85,6 +86,8 @@
private lateinit var configurationController: ConfigurationController
@Mock
private lateinit var uniqueObjectHostView: UniqueObjectHostView
+ @Mock
+ private lateinit var dreamOverlayStateController: DreamOverlayStateController
@Captor
private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
@Captor
@@ -110,7 +113,8 @@
notificationLockscreenUserManager,
configurationController,
wakefulnessLifecycle,
- statusBarKeyguardViewManager)
+ statusBarKeyguardViewManager,
+ dreamOverlayStateController)
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
verify(statusBarStateController).addCallback(statusBarCallback.capture())
setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
new file mode 100644
index 0000000..29188da
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.dream;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.widget.FrameLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.MediaHost;
+import com.android.systemui.util.animation.UniqueObjectHostView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MediaComplicationViewControllerTest extends SysuiTestCase {
+ @Mock
+ private MediaHost mMediaHost;
+
+ @Mock
+ private UniqueObjectHostView mView;
+
+ @Mock
+ private FrameLayout mComplicationContainer;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mMediaHost.hostView = mView;
+ }
+
+ @Test
+ public void testMediaHostViewInteraction() {
+ final MediaComplicationViewController controller = new MediaComplicationViewController(
+ mComplicationContainer, mMediaHost);
+
+ controller.init();
+
+ controller.onViewAttached();
+ verify(mComplicationContainer).addView(eq(mView));
+
+ controller.onViewDetached();
+ verify(mComplicationContainer).removeView(eq(mView));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
new file mode 100644
index 0000000..114fc90
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.dream;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaDataManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MediaDreamSentinelTest extends SysuiTestCase {
+ @Mock
+ MediaDataManager mMediaDataManager;
+
+ @Mock
+ DreamOverlayStateController mDreamOverlayStateController;
+
+ @Mock
+ MediaDreamComplication mComplication;
+
+ final String mKey = "key";
+ final String mOldKey = "old_key";
+
+ @Mock
+ MediaData mData;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testComplicationAddition() {
+ final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
+ mDreamOverlayStateController, mComplication);
+
+ sentinel.start();
+
+ ArgumentCaptor<MediaDataManager.Listener> listenerCaptor =
+ ArgumentCaptor.forClass(MediaDataManager.Listener.class);
+ verify(mMediaDataManager).addListener(listenerCaptor.capture());
+
+ final MediaDataManager.Listener listener = listenerCaptor.getValue();
+
+ when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
+ listener.onMediaDataLoaded(mKey, mOldKey, mData, true, 0);
+ verify(mDreamOverlayStateController, never()).addComplication(any());
+
+ when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+ listener.onMediaDataLoaded(mKey, mOldKey, mData, true, 0);
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+
+ listener.onMediaDataRemoved(mKey);
+ verify(mDreamOverlayStateController, never()).removeComplication(any());
+
+ when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
+ listener.onMediaDataRemoved(mKey);
+ verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesServiceTest.kt
new file mode 100644
index 0000000..c261086
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesServiceTest.kt
@@ -0,0 +1,124 @@
+package com.android.systemui.media.nearby
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.media.INearbyMediaDevicesProvider
+import com.android.systemui.shared.media.INearbyMediaDevicesService
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback.RANGE_LONG
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback.RANGE_WITHIN_REACH
+import com.android.systemui.shared.media.NearbyDevice
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class NearbyMediaDevicesServiceTest : SysuiTestCase() {
+
+ private lateinit var service: NearbyMediaDevicesService
+ private lateinit var binderInterface: INearbyMediaDevicesService
+
+ @Before
+ fun setUp() {
+ service = NearbyMediaDevicesService()
+ binderInterface = INearbyMediaDevicesService.Stub.asInterface(service.onBind(null))
+ }
+
+ @Test
+ fun getCurrentNearbyDevices_noProviderRegistered_returnsEmptyList() {
+ assertThat(service.getCurrentNearbyDevices()).isEmpty()
+ }
+
+ @Test
+ fun getCurrentNearbyDevices_providerRegistered_returnsProviderInfo() {
+ val nearbyDevice1 = NearbyDevice("routeId1", RANGE_LONG)
+ val nearbyDevice2 = NearbyDevice("routeId2", RANGE_WITHIN_REACH)
+ val provider = object : INearbyMediaDevicesProvider.Stub() {
+ override fun getCurrentNearbyDevices(): List<NearbyDevice> {
+ return listOf(nearbyDevice1, nearbyDevice2)
+ }
+
+ override fun registerNearbyDevicesCallback(
+ callback: INearbyMediaDevicesUpdateCallback?
+ ) {}
+ override fun unregisterNearbyDevicesCallback(
+ callback: INearbyMediaDevicesUpdateCallback?
+ ) {}
+ }
+ binderInterface.registerProvider(provider)
+
+ val returnedNearbyDevices = service.getCurrentNearbyDevices()
+
+ assertThat(returnedNearbyDevices).isEqualTo(listOf(nearbyDevice1, nearbyDevice2))
+ }
+
+ @Test
+ fun registerNearbyDevicesCallback_noProviderRegistered_noCrash() {
+ // No assert, just needs no crash
+ service.registerNearbyDevicesCallback(object : INearbyMediaDevicesUpdateCallback.Stub() {
+ override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
+ })
+ }
+
+ @Test
+ fun registerNearbyDevicesCallback_providerRegistered_providerReceivesCallback() {
+ val provider = object : INearbyMediaDevicesProvider.Stub() {
+ var registeredCallback: INearbyMediaDevicesUpdateCallback? = null
+ override fun getCurrentNearbyDevices(): List<NearbyDevice> = listOf()
+
+ override fun registerNearbyDevicesCallback(
+ callback: INearbyMediaDevicesUpdateCallback?
+ ) {
+ registeredCallback = callback
+ }
+
+ override fun unregisterNearbyDevicesCallback(
+ callback: INearbyMediaDevicesUpdateCallback?
+ ) {}
+ }
+ binderInterface.registerProvider(provider)
+
+ val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
+ override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
+ }
+
+ service.registerNearbyDevicesCallback(callback)
+
+ assertThat(provider.registeredCallback).isEqualTo(callback)
+ }
+
+ @Test
+ fun unregisterNearbyDevicesCallback_noProviderRegistered_noCrash() {
+ // No assert, just needs no crash
+ service.unregisterNearbyDevicesCallback(object : INearbyMediaDevicesUpdateCallback.Stub() {
+ override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
+ })
+ }
+
+ @Test
+ fun unregisterNearbyDevicesCallback_providerRegistered_providerReceivesCallback() {
+ val provider = object : INearbyMediaDevicesProvider.Stub() {
+ var unregisteredCallback: INearbyMediaDevicesUpdateCallback? = null
+ override fun getCurrentNearbyDevices(): List<NearbyDevice> = listOf()
+
+ override fun registerNearbyDevicesCallback(
+ callback: INearbyMediaDevicesUpdateCallback?
+ ) {}
+
+ override fun unregisterNearbyDevicesCallback(
+ callback: INearbyMediaDevicesUpdateCallback?
+ ) {
+ unregisteredCallback = callback
+ }
+ }
+ binderInterface.registerProvider(provider)
+
+ val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
+ override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
+ }
+
+ service.unregisterNearbyDevicesCallback(callback)
+
+ assertThat(provider.unregisteredCallback).isEqualTo(callback)
+ }
+}
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 81ae209..5f800eb 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 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 @@
@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 @@
) { EmptyCommand() }
}
+ /* TODO(b/216318437): Revive these tests using the new SystemApis.
@Test
fun sender_moveCloserToStartCast_serviceCallbackCalled() {
commandRegistry.onShellCommand(pw, getMoveCloserToStartCastCommand())
@@ -182,6 +171,8 @@
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 6b4eebe..58f4818 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.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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
controllerSender.displayChip(transferToReceiverTriggered())
controllerSender.displayChip(
transferToReceiverSucceeded(
- object : IUndoTransferCallback.Stub() {
+ object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {}
}
)
@@ -327,13 +330,13 @@
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 64542cb..0000000
--- 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 354bb51..f5fa0d0 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.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 @@
@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 @@
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 @@
@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 f43e68f..26aa37d 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 @@
@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 @@
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 59948d3..09c6d9e 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 @@
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 @@
uiEventLogger,
qsLogger,
dumpManager,
- quickQsBrightnessController,
- footerActionsController
+ quickQsBrightnessController
)
controller.init()
@@ -128,14 +125,12 @@
}
@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/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
new file mode 100644
index 0000000..3231415
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
@@ -0,0 +1,117 @@
+package com.android.systemui.shared.animation
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
+
+ @Mock private lateinit var progressProvider: UnfoldTransitionProgressProvider
+
+ @Mock private lateinit var parent: ViewGroup
+
+ @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
+
+ private lateinit var animator: UnfoldConstantTranslateAnimator
+ private lateinit var progressListener: TransitionProgressListener
+
+ private val viewsIdToRegister =
+ setOf(
+ ViewIdToTranslate(LEFT_VIEW_ID, Direction.LEFT),
+ ViewIdToTranslate(RIGHT_VIEW_ID, Direction.RIGHT))
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ animator =
+ UnfoldConstantTranslateAnimator(viewsIdToRegister, progressProvider)
+
+ animator.init(parent, MAX_TRANSLATION)
+
+ verify(progressProvider).addCallback(progressListenerCaptor.capture())
+ progressListener = progressListenerCaptor.value
+ }
+
+ @Test
+ fun onTransition_noMatchingIds() {
+ // GIVEN no views matching any ids
+ // WHEN the transition starts
+ progressListener.onTransitionStarted()
+ progressListener.onTransitionProgress(.1f)
+
+ // THEN nothing... no exceptions
+ }
+
+ @Test
+ fun onTransition_oneMovesLeft() {
+ // GIVEN one view with a matching id
+ val view = View(context)
+ whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(view)
+
+ moveAndValidate(listOf(view to LEFT))
+ }
+
+ @Test
+ fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() {
+ // GIVEN two views with a matching id
+ val leftView = View(context)
+ val rightView = View(context)
+ whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(leftView)
+ whenever(parent.findViewById<View>(RIGHT_VIEW_ID)).thenReturn(rightView)
+
+ moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+ moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+ }
+
+ private fun moveAndValidate(list: List<Pair<View, Int>>) {
+ // Compare values as ints because -0f != 0f
+
+ // WHEN the transition starts
+ progressListener.onTransitionStarted()
+ progressListener.onTransitionProgress(0f)
+
+ list.forEach { (view, direction) ->
+ assertEquals((-MAX_TRANSLATION * direction).toInt(), view.translationX.toInt())
+ }
+
+ // WHEN the transition progresses, translation is updated
+ progressListener.onTransitionProgress(.5f)
+ list.forEach { (view, direction) ->
+ assertEquals((-MAX_TRANSLATION / 2f * direction).toInt(), view.translationX.toInt())
+ }
+
+ // WHEN the transition ends, translation is completed
+ progressListener.onTransitionProgress(1f)
+ progressListener.onTransitionFinished()
+ list.forEach { (view, _) -> assertEquals(0, view.translationX.toInt()) }
+ }
+
+ companion object {
+ private val LEFT = Direction.LEFT.multiplier.toInt()
+ private val RIGHT = Direction.RIGHT.multiplier.toInt()
+
+ private const val MAX_TRANSLATION = 42f
+
+ private const val LEFT_VIEW_ID = 1
+ private const val RIGHT_VIEW_ID = 2
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 42647f7..9076e16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -169,8 +169,6 @@
transitionController.goToLockedShade(null)
whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE)
transitionController.goToLockedShade(null)
- whenever(statusbarStateController.state).thenReturn(StatusBarState.FULLSCREEN_USER_SWITCHER)
- transitionController.goToLockedShade(null)
verify(statusbarStateController, never()).setState(anyInt())
}
@@ -227,7 +225,7 @@
fun testDragDownAmountDoesntCallOutInLockedDownShade() {
whenever(nsslController.isInLockedDownShade).thenReturn(true)
transitionController.dragDownAmount = 10f
- verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
+ verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat())
verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(),
@@ -238,7 +236,7 @@
@Test
fun testDragDownAmountCallsOut() {
transitionController.dragDownAmount = 10f
- verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
+ verify(nsslController).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
verify(scrimController).setTransitionToFullShadeProgress(anyFloat())
verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 83f1d87..7fafb24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -35,6 +35,7 @@
import androidx.test.filters.SmallTest;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
@@ -56,6 +57,7 @@
import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.wm.shell.bubbles.Bubbles;
import com.google.android.collect.Lists;
@@ -123,7 +125,9 @@
mock(DynamicChildBindController.class),
mock(LowPriorityInflationHelper.class),
mock(AssistantFeedbackController.class),
- mNotifPipelineFlags);
+ mNotifPipelineFlags,
+ mock(KeyguardUpdateMonitor.class),
+ mock(KeyguardStateController.class));
mViewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index b736f38..a5ea897 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -61,18 +61,16 @@
@Test
fun testChangeState_logged() {
TestableLooper.get(this).runWithLooper {
- controller.state = StatusBarState.FULLSCREEN_USER_SWITCHER
controller.state = StatusBarState.KEYGUARD
controller.state = StatusBarState.SHADE
controller.state = StatusBarState.SHADE_LOCKED
}
val logs = uiEventLogger.logs
- assertEquals(4, logs.size)
+ assertEquals(3, logs.size)
val ids = logs.map(UiEventLoggerFake.FakeUiEvent::eventId)
- assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER.id, ids[0])
- assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_KEYGUARD.id, ids[1])
- assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE.id, ids[2])
- assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE_LOCKED.id, ids[3])
+ assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_KEYGUARD.id, ids[0])
+ assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE.id, ids[1])
+ assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE_LOCKED.id, ids[2])
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
index b5b2f1f..79a2008 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
@@ -33,18 +33,16 @@
StatusBarStateEvent.STATUS_BAR_STATE_SHADE,
StatusBarStateEvent.STATUS_BAR_STATE_SHADE_LOCKED,
StatusBarStateEvent.STATUS_BAR_STATE_KEYGUARD,
- StatusBarStateEvent.STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER,
StatusBarStateEvent.STATUS_BAR_STATE_UNKNOWN
)
val states = listOf(
StatusBarState.SHADE,
StatusBarState.SHADE_LOCKED,
StatusBarState.KEYGUARD,
- StatusBarState.FULLSCREEN_USER_SWITCHER,
-1
)
events.zip(states).forEach { (event, state) ->
assertEquals(event, StatusBarStateEvent.fromState(state))
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 5fd4174..3f84c16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -19,8 +19,11 @@
import android.os.UserHandle
import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.DynamicPrivacyController
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -28,9 +31,12 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import org.junit.Test
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -40,9 +46,13 @@
val dynamicPrivacyController: DynamicPrivacyController = mock()
val lockscreenUserManager: NotificationLockscreenUserManager = mock()
val pipeline: NotifPipeline = mock()
+ val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock()
+ val statusBarStateController: StatusBarStateController = mock()
+ val keyguardStateController: KeyguardStateController = mock()
val coordinator: SensitiveContentCoordinator = SensitiveContentCoordinatorModule
- .provideCoordinator(dynamicPrivacyController, lockscreenUserManager)
+ .provideCoordinator(dynamicPrivacyController, lockscreenUserManager,
+ keyguardUpdateMonitor, statusBarStateController, keyguardStateController)
@Test
fun onDynamicPrivacyChanged_invokeInvalidationListener() {
@@ -190,6 +200,28 @@
verify(entry.representativeEntry!!).setSensitive(true, true)
}
+ @Test
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+ whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD)
+ whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any()))
+ .thenReturn(true)
+
+ val entry = fakeNotification(2, true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!, never()).setSensitive(any(), any())
+ }
+
private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry {
val mockUserHandle = mock<UserHandle>().apply {
whenever(identifier).thenReturn(notifUserId)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
index 15ff555..ab71264 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.render;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import android.content.Context;
import android.testing.AndroidTestingRunner;
@@ -138,7 +139,7 @@
}
@Test
- public void testRemovedGroupsAreKeptTogether() {
+ public void testRemovedGroupsAreBrokenApart() {
// GIVEN a preexisting tree with a group
applySpecAndCheck(
node(mController1),
@@ -154,10 +155,10 @@
node(mController1)
);
- // THEN the group children are still attached to their parent
- assertEquals(mController2.getView(), mController3.getView().getParent());
- assertEquals(mController2.getView(), mController4.getView().getParent());
- assertEquals(mController2.getView(), mController5.getView().getParent());
+ // THEN the group children are no longer attached to their parent
+ assertNull(mController3.getView().getParent());
+ assertNull(mController4.getView().getParent());
+ assertNull(mController5.getView().getParent());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
new file mode 100644
index 0000000..d280f54
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -0,0 +1,153 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.NotificationShelf
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.*
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * Tests for {@link NotificationShelf}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationShelfTest : SysuiTestCase() {
+
+ private val shelf = NotificationShelf(context, /* attrs */ null)
+ private val shelfState = shelf.viewState as NotificationShelf.ShelfState
+ private val ambientState = mock(AmbientState::class.java)
+
+ @Before
+ fun setUp() {
+ shelf.bind(ambientState, /* hostLayoutController */ null)
+ shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5)
+ }
+
+ @Test
+ fun testShadeWidth_BasedOnFractionToShade() {
+ setFractionToShade(0f)
+ setOnLockscreen(true)
+
+ shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10)
+ assertTrue(shelfState.actualWidth == 10)
+
+ shelf.updateStateWidth(shelfState, /* fraction */ 0.5f, /* shortestWidth */ 10)
+ assertTrue(shelfState.actualWidth == 20)
+
+ shelf.updateStateWidth(shelfState, /* fraction */ 1f, /* shortestWidth */ 10)
+ assertTrue(shelfState.actualWidth == 30)
+ }
+
+ @Test
+ fun testShelfIsLong_WhenNotOnLockscreen() {
+ setFractionToShade(0f)
+ setOnLockscreen(false)
+
+ shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10)
+ assertTrue(shelfState.actualWidth == 30)
+ }
+
+ @Test
+ fun testX_inViewForClick() {
+ val isXInView = shelf.isXInView(
+ /* localX */ 5f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertTrue(isXInView)
+ }
+
+ @Test
+ fun testXSlop_inViewForClick() {
+ val isLeftXSlopInView = shelf.isXInView(
+ /* localX */ -3f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertTrue(isLeftXSlopInView)
+
+ val isRightXSlopInView = shelf.isXInView(
+ /* localX */ 13f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertTrue(isRightXSlopInView)
+ }
+
+ @Test
+ fun testX_notInViewForClick() {
+ val isXLeftOfShelfInView = shelf.isXInView(
+ /* localX */ -10f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertFalse(isXLeftOfShelfInView)
+
+ val isXRightOfShelfInView = shelf.isXInView(
+ /* localX */ 20f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertFalse(isXRightOfShelfInView)
+ }
+
+ @Test
+ fun testY_inViewForClick() {
+ val isYInView = shelf.isYInView(
+ /* localY */ 5f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 10f)
+ assertTrue(isYInView)
+ }
+
+ @Test
+ fun testYSlop_inViewForClick() {
+ val isTopYSlopInView = shelf.isYInView(
+ /* localY */ -3f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 10f)
+ assertTrue(isTopYSlopInView)
+
+ val isBottomYSlopInView = shelf.isYInView(
+ /* localY */ 13f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 10f)
+ assertTrue(isBottomYSlopInView)
+ }
+
+ @Test
+ fun testY_notInViewForClick() {
+ val isYAboveShelfInView = shelf.isYInView(
+ /* localY */ -10f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 5f)
+ assertFalse(isYAboveShelfInView)
+
+ val isYBelowShelfInView = shelf.isYInView(
+ /* localY */ 15f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 5f)
+ assertFalse(isYBelowShelfInView)
+ }
+
+ private fun setFractionToShade(fraction: Float) {
+ shelf.setFractionToShade(fraction)
+ }
+
+ private fun setOnLockscreen(isOnLockscreen: Boolean) {
+ whenever(ambientState.isOnKeyguard).thenReturn(isOnLockscreen)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index ea68143..1da9bbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -69,10 +69,9 @@
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
- val closeHandleUnderlapHeight =
- context.resources.getDimensionPixelSize(R.dimen.close_handle_underlap)
- val fullHeight =
- ambientState.layoutMaxHeight + closeHandleUnderlapHeight - ambientState.stackY
+ val marginBottom =
+ context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
+ val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY
val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 8c7d22d..fb232ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -38,6 +38,7 @@
import android.testing.TestableResources;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
@@ -110,6 +111,8 @@
private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock
private SessionTracker mSessionTracker;
+ @Mock
+ private LatencyTracker mLatencyTracker;
private BiometricUnlockController mBiometricUnlockController;
@Before
@@ -133,7 +136,7 @@
mMetricsLogger, mDumpManager, mPowerManager,
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController,
- mSessionTracker);
+ mSessionTracker, mLatencyTracker);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 36a4c1e..01e9822e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -23,9 +23,13 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
@@ -45,6 +49,7 @@
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
@@ -52,6 +57,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -87,6 +93,12 @@
private SysuiStatusBarStateController mStatusBarStateController;
@Mock
private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider;
+ @Mock
+ private UserManager mUserManager;
+ @Captor
+ private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor;
+ @Captor
+ private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider;
private KeyguardStatusBarView mKeyguardStatusBarView;
@@ -101,8 +113,8 @@
allowTestableLooperAsMainThread();
TestableLooper.get(this).runWithLooper(() -> {
mKeyguardStatusBarView =
- (KeyguardStatusBarView) LayoutInflater.from(mContext)
- .inflate(R.layout.keyguard_status_bar, null);
+ spy((KeyguardStatusBarView) LayoutInflater.from(mContext)
+ .inflate(R.layout.keyguard_status_bar, null));
});
mController = new KeyguardStatusBarViewController(
@@ -121,7 +133,8 @@
mKeyguardUpdateMonitor,
mBiometricUnlockController,
mStatusBarStateController,
- mStatusBarContentInsetsProvider
+ mStatusBarContentInsetsProvider,
+ mUserManager
);
}
@@ -133,6 +146,31 @@
verify(mAnimationScheduler).addCallback(any());
verify(mUserInfoController).addCallback(any());
verify(mStatusBarIconController).addIconGroup(any());
+ verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+ }
+
+ @Test
+ public void onConfigurationChanged_updatesUserSwitcherVisibility() {
+ mController.onViewAttached();
+ verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
+ clearInvocations(mUserManager);
+ clearInvocations(mKeyguardStatusBarView);
+
+ mConfigurationListenerCaptor.getValue().onConfigChanged(null);
+ verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+ verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
+ }
+
+ @Test
+ public void onKeyguardVisibilityChanged_updatesUserSwitcherVisibility() {
+ mController.onViewAttached();
+ verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
+ clearInvocations(mUserManager);
+ clearInvocations(mKeyguardStatusBarView);
+
+ mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
+ verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+ verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
index 5d7b154..d002cebe5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
@@ -250,15 +250,17 @@
@Notification.GroupAlertBehavior int priorityGroupAlert,
@Notification.GroupAlertBehavior int siblingGroupAlert,
boolean expectAlertOverride) {
+ long when = 10000;
// Create entries in an order so that the priority entry can be deemed the newest child.
NotificationEntry[] siblings = new NotificationEntry[numSiblings];
for (int i = 0; i < numSiblings; i++) {
- siblings[i] = mGroupTestHelper.createChildNotification(siblingGroupAlert, i, "sibling");
+ siblings[i] = mGroupTestHelper
+ .createChildNotification(siblingGroupAlert, i, "sibling", ++when);
}
NotificationEntry priorityEntry =
- mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority");
+ mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority", ++when);
NotificationEntry summaryEntry =
- mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary");
+ mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary", ++when);
// The priority entry is an important conversation.
when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
@@ -322,17 +324,19 @@
@Test
public void testAlertOverrideWhenUpdatingSummaryAtEnd() {
+ long when = 10000;
int numSiblings = 2;
int groupAlert = Notification.GROUP_ALERT_SUMMARY;
// Create entries in an order so that the priority entry can be deemed the newest child.
NotificationEntry[] siblings = new NotificationEntry[numSiblings];
for (int i = 0; i < numSiblings; i++) {
- siblings[i] = mGroupTestHelper.createChildNotification(groupAlert, i, "sibling");
+ siblings[i] =
+ mGroupTestHelper.createChildNotification(groupAlert, i, "sibling", ++when);
}
NotificationEntry priorityEntry =
- mGroupTestHelper.createChildNotification(groupAlert, 0, "priority");
+ mGroupTestHelper.createChildNotification(groupAlert, 0, "priority", ++when);
NotificationEntry summaryEntry =
- mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary");
+ mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary", ++when);
// The priority entry is an important conversation.
when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
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 337e64592..bbb2346 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.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 @@
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 @@
notificationsQSContainerController = NotificationsQSContainerController(
notificationsQSContainer,
navigationModeController,
- overviewProxyService
+ overviewProxyService,
+ featureFlags
)
whenever(notificationsQSContainer.defaultNotificationsMarginBottom)
.thenReturn(NOTIFICATIONS_MARGIN)
@@ -85,6 +90,26 @@
@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 @@
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 @@
@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 @@
@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 @@
@Test
fun testTaskbarNotVisibleInSinglePaneShade() {
notificationsQSContainerController.splitShadeEnabled = false
+ useNewFooter(false)
+
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
insets = emptyInsets())
@@ -159,14 +243,56 @@
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 @@
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,8 @@
@Test
fun testDetailShowingInSplitShade() {
notificationsQSContainerController.splitShadeEnabled = true
+ useNewFooter(false)
+
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
insets = windowInsets().withStableBottom())
@@ -215,6 +365,36 @@
then(expectedContainerPadding = 0)
}
+ @Test
+ fun testDetailShowingInSplitShade_newFooter() {
+ notificationsQSContainerController.splitShadeEnabled = true
+ useNewFooter(true)
+
+ 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 testNotificationsMarginBottomIsUpdated() {
+ notificationsQSContainerController.splitShadeEnabled = true
+ verify(notificationsQSContainer).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
+
+ whenever(notificationsQSContainer.defaultNotificationsMarginBottom).thenReturn(100)
+ notificationsQSContainerController.updateMargins()
+ notificationsQSContainerController.splitShadeEnabled = false
+
+ verify(notificationsQSContainer).setNotificationsMarginBottom(100)
+ }
+
private fun given(
taskbarVisible: Boolean,
navigationMode: Int,
@@ -234,7 +414,13 @@
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)
}
@@ -251,4 +437,8 @@
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/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 5d80bca..107ba81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -36,6 +36,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardMessageArea;
import com.android.keyguard.KeyguardMessageAreaController;
@@ -43,6 +44,7 @@
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -97,6 +99,10 @@
private KeyguardMessageArea mKeyguardMessageArea;
@Mock
private ShadeController mShadeController;
+ @Mock
+ private DreamOverlayStateController mDreamOverlayStateController;
+ @Mock
+ private LatencyTracker mLatencyTracker;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -116,6 +122,7 @@
mStatusBarStateController,
mock(ConfigurationController.class),
mKeyguardUpdateMonitor,
+ mDreamOverlayStateController,
mock(NavigationModeController.class),
mock(DockManager.class),
mock(NotificationShadeWindowController.class),
@@ -123,7 +130,8 @@
mock(NotificationMediaManager.class),
mKeyguardBouncerFactory,
mKeyguardMessageAreaFactory,
- () -> mShadeController);
+ () -> mShadeController,
+ mLatencyTracker);
mStatusBarKeyguardViewManager.registerStatusBar(
mStatusBar,
mNotificationPanelView,
@@ -167,17 +175,6 @@
}
@Test
- public void onPanelExpansionChanged_neverHidesFullscreenBouncer() {
- // TODO: StatusBar should not be here, mBouncer.isFullscreenBouncer() should do the same.
- when(mStatusBar.isFullScreenUserSwitcherState()).thenReturn(true);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(
- /* fraction= */ 0.5f,
- /* expanded= */ false,
- /* tracking= */ true);
- verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
- }
-
- @Test
public void onPanelExpansionChanged_neverHidesScrimmedBouncer() {
when(mBouncer.isShowing()).thenReturn(true);
when(mBouncer.isScrimmed()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index b7c00fe..90b93e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -87,6 +87,7 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentService;
@@ -285,6 +286,7 @@
@Mock private NotifLiveDataStore mNotifLiveDataStore;
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private DeviceStateManager mDeviceStateManager;
+ @Mock private DreamOverlayStateController mDreamOverlayStateController;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@@ -474,7 +476,8 @@
mActivityLaunchAnimator,
mNotifPipelineFlags,
mJankMonitor,
- mDeviceStateManager);
+ mDeviceStateManager,
+ mDreamOverlayStateController);
when(mKeyguardViewMediator.registerStatusBar(
any(StatusBar.class),
any(NotificationPanelViewController.class),
@@ -882,15 +885,6 @@
}
@Test
- public void testSetState_changesIsFullScreenUserSwitcherState() {
- mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
- assertFalse(mStatusBar.isFullScreenUserSwitcherState());
-
- mStatusBar.setBarStateForTest(StatusBarState.FULLSCREEN_USER_SWITCHER);
- assertTrue(mStatusBar.isFullScreenUserSwitcherState());
- }
-
- @Test
public void testShowKeyguardImplementation_setsState() {
when(mLockscreenUserManager.getCurrentProfiles()).thenReturn(new SparseArray<>());
@@ -900,12 +894,6 @@
mStatusBar.showKeyguardImpl();
verify(mStatusBarStateController).setState(
eq(StatusBarState.KEYGUARD), eq(false) /* force */);
-
- // If useFullscreenUserSwitcher is true, state is set to FULLSCREEN_USER_SWITCHER.
- when(mUserSwitcherController.useFullscreenUserSwitcher()).thenReturn(true);
- mStatusBar.showKeyguardImpl();
- verify(mStatusBarStateController).setState(
- eq(StatusBarState.FULLSCREEN_USER_SWITCHER), eq(false) /* force */);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index fa2a906..9a7e702 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -400,4 +400,16 @@
assertEquals(fgUserName, userSwitcherController.currentUserName)
}
+
+ @Test
+ fun isSystemUser_currentUserIsSystemUser_shouldReturnTrue() {
+ `when`(userTracker.userId).thenReturn(UserHandle.USER_SYSTEM)
+ assertEquals(true, userSwitcherController.isSystemUser)
+ }
+
+ @Test
+ fun isSystemUser_currentUserIsNotSystemUser_shouldReturnFalse() {
+ `when`(userTracker.userId).thenReturn(1)
+ assertEquals(false, userSwitcherController.isSystemUser)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
new file mode 100644
index 0000000..a2fd288
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.util.service;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class PackageObserverTest extends SysuiTestCase {
+ @Mock
+ Context mContext;
+
+ @Mock
+ Observer.Callback mCallback;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testChange() {
+ final PackageObserver observer = new PackageObserver(mContext,
+ ComponentName.unflattenFromString("com.foo.bar/baz"));
+ final ArgumentCaptor<BroadcastReceiver> receiverCapture =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+ observer.addCallback(mCallback);
+
+ // Verify broadcast receiver registered.
+ verify(mContext).registerReceiver(receiverCapture.capture(), any(), anyInt());
+
+ // Simulate package change.
+ receiverCapture.getValue().onReceive(mContext, new Intent());
+
+ // Check that callback was informed.
+ verify(mCallback).onSourceChanged();
+
+ observer.removeCallback(mCallback);
+
+ // Make sure receiver is unregistered on last callback removal
+ verify(mContext).unregisterReceiver(receiverCapture.getValue());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
new file mode 100644
index 0000000..53d4a96
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.util.service;
+
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class PersistentConnectionManagerTest extends SysuiTestCase {
+ private static final int MAX_RETRIES = 5;
+ private static final int RETRY_DELAY_MS = 1000;
+ private static final int CONNECTION_MIN_DURATION_MS = 5000;
+
+ private FakeSystemClock mFakeClock = new FakeSystemClock();
+ private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeClock);
+
+ @Mock
+ private ObservableServiceConnection<Proxy> mConnection;
+
+ @Mock
+ private Observer mObserver;
+
+ private static class Proxy {
+ }
+
+ private PersistentConnectionManager<Proxy> mConnectionManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mConnectionManager = new PersistentConnectionManager<>(
+ mFakeClock,
+ mFakeExecutor,
+ mConnection,
+ MAX_RETRIES,
+ RETRY_DELAY_MS,
+ CONNECTION_MIN_DURATION_MS,
+ mObserver);
+ }
+
+ private ObservableServiceConnection.Callback<Proxy> captureCallbackAndSend(
+ ObservableServiceConnection<Proxy> mConnection, Proxy proxy) {
+ ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
+ ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
+
+ verify(mConnection).addCallback(connectionCallbackCaptor.capture());
+ verify(mConnection).bind();
+ Mockito.clearInvocations(mConnection);
+
+ final ObservableServiceConnection.Callback callback = connectionCallbackCaptor.getValue();
+ if (proxy != null) {
+ callback.onConnected(mConnection, proxy);
+ } else {
+ callback.onDisconnected(mConnection, 0);
+ }
+
+ return callback;
+ }
+
+ /**
+ * Validates initial connection.
+ */
+ @Test
+ public void testConnect() {
+ mConnectionManager.start();
+ captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
+ }
+
+ /**
+ * Ensures reconnection on disconnect.
+ */
+ @Test
+ public void testRetryOnBindFailure() {
+ mConnectionManager.start();
+ ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
+ ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
+
+ verify(mConnection).addCallback(connectionCallbackCaptor.capture());
+
+ // Verify attempts happen. Note that we account for the retries plus initial attempt, which
+ // is not scheduled.
+ for (int attemptCount = 0; attemptCount < MAX_RETRIES + 1; attemptCount++) {
+ verify(mConnection).bind();
+ Mockito.clearInvocations(mConnection);
+ connectionCallbackCaptor.getValue().onDisconnected(mConnection, 0);
+ mFakeExecutor.advanceClockToNext();
+ mFakeExecutor.runAllReady();
+ }
+ }
+
+ /**
+ * Ensures rebind on package change.
+ */
+ @Test
+ public void testAttemptOnPackageChange() {
+ mConnectionManager.start();
+ verify(mConnection).bind();
+ ArgumentCaptor<Observer.Callback> callbackCaptor =
+ ArgumentCaptor.forClass(Observer.Callback.class);
+ captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
+
+ verify(mObserver).addCallback(callbackCaptor.capture());
+
+ callbackCaptor.getValue().onSourceChanged();
+ verify(mConnection).bind();
+ }
+}
diff --git a/services/Android.bp b/services/Android.bp
index af70692..b0a5c66 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -102,6 +102,7 @@
":services.usage-sources",
":services.usb-sources",
":services.voiceinteraction-sources",
+ ":services.wallpapereffectsgeneration-sources",
":services.wifi-sources",
],
visibility: ["//visibility:private"],
@@ -158,6 +159,7 @@
"services.usage",
"services.usb",
"services.voiceinteraction",
+ "services.wallpapereffectsgeneration",
"services.wifi",
"service-blobstore",
"service-jobscheduler",
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 6744ea8..803177b 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -295,6 +295,21 @@
case AccessibilityService.GLOBAL_ACTION_KEYCODE_HEADSETHOOK :
sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK);
return true;
+ case AccessibilityService.GLOBAL_ACTION_DPAD_UP:
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_UP);
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_DPAD_DOWN:
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_DOWN);
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_DPAD_LEFT:
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_LEFT);
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_DPAD_RIGHT:
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_RIGHT);
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_DPAD_CENTER:
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
+ return true;
default:
Slog.e(TAG, "Invalid action id: " + actionId);
return false;
diff --git a/services/api/current.txt b/services/api/current.txt
index 50f0052..dcf7e64 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -39,6 +39,7 @@
public interface ActivityManagerLocal {
method public boolean canStartForegroundService(int, int, @NonNull String);
+ method public boolean startAndBindSupplementalProcessService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int) throws android.os.TransactionTooLargeException;
}
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 2168fb1..a65d5b3 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -3346,8 +3346,10 @@
// Isolate the changes relating to RROs. The app info must be copied to prevent
// affecting other parts of system server that may have cached this app info.
oldAppInfo = new ApplicationInfo(oldAppInfo);
- oldAppInfo.overlayPaths = newAppInfo.overlayPaths.clone();
- oldAppInfo.resourceDirs = newAppInfo.resourceDirs.clone();
+ oldAppInfo.overlayPaths = newAppInfo.overlayPaths == null
+ ? null : newAppInfo.overlayPaths.clone();
+ oldAppInfo.resourceDirs = newAppInfo.resourceDirs == null
+ ? null : newAppInfo.resourceDirs.clone();
provider.info.providerInfo.applicationInfo = oldAppInfo;
for (int j = 0, M = provider.widgets.size(); j < M; j++) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 095c1fc..76ee728 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -17,6 +17,7 @@
package com.android.server.autofill;
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
+import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
import static android.service.autofill.FillRequest.FLAG_ACTIVITY_START;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
@@ -47,13 +48,16 @@
import android.app.Activity;
import android.app.ActivityTaskManager;
import android.app.IAssistDataReceiver;
+import android.app.PendingIntent;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.AutofillOverlay;
import android.app.assist.AssistStructure.ViewNode;
+import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
@@ -148,6 +152,8 @@
AutoFillUI.AutoFillUiCallback, ValueFinder {
private static final String TAG = "AutofillSession";
+ private static final String ACTION_DELAYED_FILL =
+ "android.service.autofill.action.DELAYED_FILL";
private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
final Object mLock;
@@ -155,6 +161,7 @@
private final AutofillManagerServiceImpl mService;
private final Handler mHandler;
private final AutoFillUI mUi;
+ @NonNull private final Context mContext;
private final MetricsLogger mMetricsLogger = new MetricsLogger();
@@ -269,6 +276,12 @@
*/
private boolean mHasCallback;
+ @GuardedBy("mLock")
+ private boolean mDelayedFillBroadcastReceiverRegistered;
+
+ @GuardedBy("mLock")
+ private PendingIntent mDelayedFillPendingIntent;
+
/**
* Extras sent by service on {@code onFillRequest()} calls; the most recent non-null extra is
* saved and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls.
@@ -356,6 +369,32 @@
private final AccessibilityManager mAccessibilityManager;
+ // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
+ // new one per Session.
+ private final BroadcastReceiver mDelayedFillBroadcastReceiver =
+ new BroadcastReceiver() {
+ // ErrorProne says mAssistReceiver#processDelayedFillLocked needs to be guarded by
+ // 'Session.this.mLock', which is the same as mLock.
+ @SuppressWarnings("GuardedBy")
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (!intent.getAction().equals(ACTION_DELAYED_FILL)) {
+ Slog.wtf(TAG, "Unexpected action is received.");
+ return;
+ }
+ if (!intent.hasExtra(EXTRA_REQUEST_ID)) {
+ Slog.e(TAG, "Delay fill action is missing request id extra.");
+ return;
+ }
+ Slog.v(TAG, "mDelayedFillBroadcastReceiver delayed fill action received");
+ synchronized (mLock) {
+ int requestId = intent.getIntExtra(EXTRA_REQUEST_ID, 0);
+ FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
+ mAssistReceiver.processDelayedFillLocked(requestId, response);
+ }
+ }
+ };
+
void onSwitchInputMethodLocked() {
// One caveat is that for the case where the focus is on a field for which regular autofill
// returns null, and augmented autofill is triggered, and then the user switches the input
@@ -408,31 +447,24 @@
*/
private final class SessionFlags {
/** Whether autofill is disabled by the service */
- @GuardedBy("mLock")
private boolean mAutofillDisabled;
/** Whether the autofill service supports inline suggestions */
- @GuardedBy("mLock")
private boolean mInlineSupportedByService;
/** True if session is for augmented only */
- @GuardedBy("mLock")
private boolean mAugmentedAutofillOnly;
/** Whether the session is currently showing the SaveUi. */
- @GuardedBy("mLock")
private boolean mShowingSaveUi;
/** Whether the current {@link FillResponse} is expired. */
- @GuardedBy("mLock")
private boolean mExpiredResponse;
/** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */
- @GuardedBy("mLock")
private boolean mClientSuggestionsEnabled;
/** Whether the fill dialog UI is disabled. */
- @GuardedBy("mLock")
private boolean mFillDialogDisabled;
}
@@ -447,6 +479,8 @@
private InlineSuggestionsRequest mPendingInlineSuggestionsRequest;
@GuardedBy("mLock")
private FillRequest mPendingFillRequest;
+ @GuardedBy("mLock")
+ private FillRequest mLastFillRequest;
@Nullable Consumer<InlineSuggestionsRequest> newAutofillRequestLocked(ViewState viewState,
boolean isInlineRequest) {
@@ -473,6 +507,7 @@
mPendingInlineSuggestionsRequest = inlineRequest;
}
+ @GuardedBy("mLock")
void maybeRequestFillFromServiceLocked() {
if (mPendingFillRequest == null) {
return;
@@ -490,9 +525,12 @@
mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
mPendingFillRequest.getFillContexts(),
mPendingFillRequest.getClientState(),
- mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest);
+ mPendingFillRequest.getFlags(),
+ mPendingInlineSuggestionsRequest,
+ mPendingFillRequest.getDelayedFillIntentSender());
}
}
+ mLastFillRequest = mPendingFillRequest;
mRemoteFillService.onFillRequest(mPendingFillRequest);
mPendingInlineSuggestionsRequest = null;
@@ -594,8 +632,12 @@
final ArrayList<FillContext> contexts =
mergePreviousSessionLocked(/* forSave= */ false);
+ mDelayedFillPendingIntent = createPendingIntent(requestId);
request = new FillRequest(requestId, contexts, mClientState, flags,
- /*inlineSuggestionsRequest=*/null);
+ /*inlineSuggestionsRequest=*/ null,
+ /*delayedFillIntentSender=*/ mDelayedFillPendingIntent == null
+ ? null
+ : mDelayedFillPendingIntent.getIntentSender());
mPendingFillRequest = request;
maybeRequestFillFromServiceLocked();
@@ -610,7 +652,70 @@
public void onHandleAssistScreenshot(Bitmap screenshot) {
// Do nothing
}
- };
+
+ @GuardedBy("mLock")
+ void processDelayedFillLocked(int requestId, FillResponse response) {
+ if (mLastFillRequest != null && requestId == mLastFillRequest.getId()) {
+ Slog.v(TAG, "processDelayedFillLocked: "
+ + "calling onFillRequestSuccess with new response");
+ onFillRequestSuccess(requestId, response,
+ mService.getServicePackageName(), mLastFillRequest.getFlags());
+ }
+ }
+ }
+
+ /** Creates {@link PendingIntent} for autofill service to send a delayed fill. */
+ private PendingIntent createPendingIntent(int requestId) {
+ Slog.d(TAG, "createPendingIntent for request " + requestId);
+ PendingIntent pendingIntent;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Intent intent = new Intent(ACTION_DELAYED_FILL).setPackage("android")
+ .putExtra(EXTRA_REQUEST_ID, requestId);
+ pendingIntent = PendingIntent.getBroadcast(
+ mContext, this.id, intent,
+ PendingIntent.FLAG_MUTABLE
+ | PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_CANCEL_CURRENT);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return pendingIntent;
+ }
+
+ @GuardedBy("mLock")
+ private void clearPendingIntentLocked() {
+ Slog.d(TAG, "clearPendingIntentLocked");
+ if (mDelayedFillPendingIntent == null) {
+ return;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mDelayedFillPendingIntent.cancel();
+ mDelayedFillPendingIntent = null;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void registerDelayedFillBroadcastLocked() {
+ if (!mDelayedFillBroadcastReceiverRegistered) {
+ Slog.v(TAG, "registerDelayedFillBroadcastLocked()");
+ IntentFilter intentFilter = new IntentFilter(ACTION_DELAYED_FILL);
+ mContext.registerReceiver(mDelayedFillBroadcastReceiver, intentFilter);
+ mDelayedFillBroadcastReceiverRegistered = true;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void unregisterDelayedFillBroadcastLocked() {
+ if (mDelayedFillBroadcastReceiverRegistered) {
+ Slog.v(TAG, "unregisterDelayedFillBroadcastLocked()");
+ mContext.unregisterReceiver(mDelayedFillBroadcastReceiver);
+ mDelayedFillBroadcastReceiverRegistered = false;
+ }
+ }
/**
* Returns the ids of all entries in {@link #mViewStates} in the same order.
@@ -964,6 +1069,7 @@
mHasCallback = hasCallback;
mUiLatencyHistory = uiLatencyHistory;
mWtfHistory = wtfHistory;
+ mContext = context;
mComponentName = componentName;
mCompatMode = compatMode;
mSessionState = STATE_ACTIVE;
@@ -1096,6 +1202,12 @@
processNullResponseLocked(requestId, requestFlags);
return;
}
+
+ final int flags = response.getFlags();
+ if ((flags & FillResponse.FLAG_DELAY_FILL) != 0) {
+ Slog.v(TAG, "Service requested to wait for delayed fill response.");
+ registerDelayedFillBroadcastLocked();
+ }
}
mService.setLastResponse(id, response);
@@ -1206,6 +1318,7 @@
@Nullable CharSequence message) {
boolean showMessage = !TextUtils.isEmpty(message);
synchronized (mLock) {
+ unregisterDelayedFillBroadcastLocked();
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId
+ ") rejected - session: " + id + " destroyed");
@@ -1522,7 +1635,7 @@
Slog.e(TAG, "Error sending input show up notification", e);
}
}
- synchronized (Session.this.mLock) {
+ synchronized (mLock) {
// stop to show fill dialog
mSessionFlags.mFillDialogDisabled = true;
}
@@ -3259,7 +3372,7 @@
private boolean isFillDialogUiEnabled() {
// TODO read from Settings or somewhere
final boolean isSettingsEnabledFillDialog = true;
- synchronized (Session.this.mLock) {
+ synchronized (mLock) {
return isSettingsEnabledFillDialog && !mSessionFlags.mFillDialogDisabled;
}
}
@@ -3530,6 +3643,7 @@
@GuardedBy("mLock")
private void processNullResponseLocked(int requestId, int flags) {
+ unregisterDelayedFillBroadcastLocked();
if ((flags & FLAG_MANUAL_REQUEST) != 0) {
getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this);
}
@@ -3744,6 +3858,11 @@
// only if handling the current response requires it.
mUi.hideAll(this);
+ if ((newResponse.getFlags() & FillResponse.FLAG_DELAY_FILL) == 0) {
+ Slog.d(TAG, "Service did not request to wait for delayed fill response.");
+ unregisterDelayedFillBroadcastLocked();
+ }
+
final int requestId = newResponse.getRequestId();
if (sVerbose) {
Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId
@@ -4296,6 +4415,9 @@
return null;
}
+ clearPendingIntentLocked();
+ unregisterDelayedFillBroadcastLocked();
+
unlinkClientVultureLocked();
mUi.destroyAll(mPendingSaveUi, this, true);
mUi.clearCallback(this);
diff --git a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java
index f1d98f0..0509e0c 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 @@
* 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 @@
@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 cda554e..21a677b8 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 @@
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 @@
* 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();
@@ -125,7 +127,7 @@
// Update the MacAddress-to-List<Association> map if needed.
final MacAddress updatedAddress = updated.getDeviceMacAddress();
final MacAddress currentAddress = current.getDeviceMacAddress();
- macAddressChanged = Objects.equals(currentAddress, updatedAddress);
+ macAddressChanged = !Objects.equals(currentAddress, updatedAddress);
if (macAddressChanged) {
if (currentAddress != null) {
mAddressMap.get(currentAddress).remove(id);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index cfd3798..c3ab2a7 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1255,7 +1255,7 @@
}
@Override
- public void onDeviceDisconnected(BluetoothDevice device, @DisconnectReason int reason) {
+ public void onDeviceDisconnected(BluetoothDevice device, int reason) {
Slog.d(LOG_TAG, device.getAddress() + " disconnected w/ reason: (" + reason + ") "
+ BluetoothAdapter.BluetoothConnectionCallback.disconnectReasonText(reason));
CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress());
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index f2e66077..fd13085 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 @@
}
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 6055a81..8ac741a 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.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.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 @@
* 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 da33b44..d0cc122 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 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.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 @@
* </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 @@
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 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 @@
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 @@
// 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 @@
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 @@
}
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 @@
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 76340fc..904283f 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 @@
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 @@
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 @@
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 @@
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 @@
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 0eb6b8d..a771e7b 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -21,8 +21,6 @@
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_PREVIOUS_STATE;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
-import static android.bluetooth.BluetoothAdapter.STATE_BLE_ON;
-import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.bluetooth.BluetoothAdapter.nameForState;
import static android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED;
import static android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED;
@@ -35,6 +33,7 @@
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;
@@ -72,7 +71,6 @@
@SuppressLint("LongLogTag")
class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
- private static final boolean DEBUG = false;
private static final String TAG = "CompanionDevice_PresenceMonitor_BLE";
/**
@@ -156,7 +154,7 @@
private void checkBleState() {
enforceInitialized();
- final boolean bleAvailable = isBleAvailable();
+ final boolean bleAvailable = mBtAdapter.isLeEnabled();
if (DEBUG) {
Log.i(TAG, "checkBleState() bleAvailable=" + bleAvailable);
}
@@ -183,16 +181,6 @@
}
}
- /**
- * A duplicate of {@code BluetoothAdapter.getLeAccess()} method which has the package-private
- * access level, so it's not accessible from here.
- */
- private boolean isBleAvailable() {
- final int state = mBtAdapter.getLeState();
- if (DEBUG) Log.d(TAG, "getLeAccess() state=" + nameForBtState(state));
- return state == STATE_ON || state == STATE_BLE_ON;
- }
-
@MainThread
private void startScan() {
enforceInitialized();
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 dbe866b..1ba198a 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 @@
class BluetoothCompanionDeviceConnectionListener
extends BluetoothAdapter.BluetoothConnectionCallback
implements AssociationStore.OnChangeListener {
- private static final boolean DEBUG = false;
private static final String TAG = "CompanionDevice_PresenceMonitor_BT";
interface Callback {
@@ -91,7 +91,7 @@
*/
@Override
public void onDeviceDisconnected(@NonNull BluetoothDevice device,
- @DisconnectReason int reason) {
+ int reason) {
if (DEBUG) {
Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device));
Log.d(TAG, " reason=" + disconnectReasonText(reason));
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 0000000..6371b25
--- /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/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 6c56e2f..e6bfd1f 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -18,8 +18,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.StringDef;
import android.graphics.Point;
import android.graphics.PointF;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
@@ -48,6 +51,20 @@
private static final String TAG = "VirtualInputController";
+ private static final AtomicLong sNextPhysId = new AtomicLong(1);
+
+ static final String PHYS_TYPE_KEYBOARD = "Keyboard";
+ static final String PHYS_TYPE_MOUSE = "Mouse";
+ static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen";
+ @StringDef(prefix = { "PHYS_TYPE_" }, value = {
+ PHYS_TYPE_KEYBOARD,
+ PHYS_TYPE_MOUSE,
+ PHYS_TYPE_TOUCHSCREEN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface PhysType {
+ }
+
private final Object mLock;
/* Token -> file descriptor associations. */
@@ -56,6 +73,8 @@
final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
private final NativeWrapper mNativeWrapper;
+ private final DisplayManagerInternal mDisplayManagerInternal;
+ private final InputManagerInternal mInputManagerInternal;
/**
* Because the pointer is a singleton, it can only be targeted at one display at a time. Because
@@ -73,6 +92,8 @@
mLock = lock;
mNativeWrapper = nativeWrapper;
mActivePointerDisplayId = Display.INVALID_DISPLAY;
+ mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+ mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
}
void close() {
@@ -90,7 +111,9 @@
int productId,
@NonNull IBinder deviceToken,
int displayId) {
- final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId);
+ final String phys = createPhys(PHYS_TYPE_KEYBOARD);
+ setUniqueIdAssociation(displayId, phys);
+ final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys);
if (fd < 0) {
throw new RuntimeException(
"A native error occurred when creating keyboard: " + -fd);
@@ -99,7 +122,7 @@
synchronized (mLock) {
mInputDeviceDescriptors.put(deviceToken,
new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_KEYBOARD, displayId));
+ InputDeviceDescriptor.TYPE_KEYBOARD, displayId, phys));
}
try {
deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
@@ -114,7 +137,9 @@
int productId,
@NonNull IBinder deviceToken,
int displayId) {
- final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId);
+ final String phys = createPhys(PHYS_TYPE_MOUSE);
+ setUniqueIdAssociation(displayId, phys);
+ final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys);
if (fd < 0) {
throw new RuntimeException(
"A native error occurred when creating mouse: " + -fd);
@@ -123,11 +148,9 @@
synchronized (mLock) {
mInputDeviceDescriptors.put(deviceToken,
new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_MOUSE, displayId));
- final InputManagerInternal inputManagerInternal =
- LocalServices.getService(InputManagerInternal.class);
- inputManagerInternal.setVirtualMousePointerDisplayId(displayId);
- inputManagerInternal.setPointerAcceleration(1);
+ InputDeviceDescriptor.TYPE_MOUSE, displayId, phys));
+ mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+ mInputManagerInternal.setPointerAcceleration(1);
mActivePointerDisplayId = displayId;
}
try {
@@ -144,7 +167,9 @@
@NonNull IBinder deviceToken,
int displayId,
@NonNull Point screenSize) {
- final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
+ final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
+ setUniqueIdAssociation(displayId, phys);
+ final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys,
screenSize.y, screenSize.x);
if (fd < 0) {
throw new RuntimeException(
@@ -154,7 +179,7 @@
synchronized (mLock) {
mInputDeviceDescriptors.put(deviceToken,
new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId));
+ InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId, phys));
}
try {
deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
@@ -174,6 +199,7 @@
}
token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor());
+ InputManager.getInstance().removeUniqueIdAssociation(inputDeviceDescriptor.getPhys());
// Reset values to the default if all virtual mice are unregistered, or set display
// id if there's another mouse (choose the most recent).
@@ -197,9 +223,7 @@
}
}
if (mostRecentlyCreatedMouse != null) {
- final InputManagerInternal inputManagerInternal =
- LocalServices.getService(InputManagerInternal.class);
- inputManagerInternal.setVirtualMousePointerDisplayId(
+ mInputManagerInternal.setVirtualMousePointerDisplayId(
mostRecentlyCreatedMouse.getDisplayId());
mActivePointerDisplayId = mostRecentlyCreatedMouse.getDisplayId();
} else {
@@ -209,14 +233,21 @@
}
private void resetMouseValuesLocked() {
- final InputManagerInternal inputManagerInternal =
- LocalServices.getService(InputManagerInternal.class);
- inputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
- inputManagerInternal.setPointerAcceleration(
+ mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
+ mInputManagerInternal.setPointerAcceleration(
IInputConstants.DEFAULT_POINTER_ACCELERATION);
mActivePointerDisplayId = Display.INVALID_DISPLAY;
}
+ private static String createPhys(@PhysType String type) {
+ return String.format("virtual%s:%d", type, sNextPhysId.getAndIncrement());
+ }
+
+ private void setUniqueIdAssociation(int displayId, String phys) {
+ final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId;
+ InputManager.getInstance().addUniqueIdAssociation(phys, displayUniqueId);
+ }
+
boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
synchronized (mLock) {
final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
@@ -321,17 +352,18 @@
fout.println(" creationOrder: "
+ inputDeviceDescriptor.getCreationOrderNumber());
fout.println(" type: " + inputDeviceDescriptor.getType());
+ fout.println(" phys: " + inputDeviceDescriptor.getPhys());
}
fout.println(" Active mouse display id: " + mActivePointerDisplayId);
}
}
private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
- int productId);
- private static native int nativeOpenUinputMouse(String deviceName, int vendorId,
- int productId);
+ int productId, String phys);
+ private static native int nativeOpenUinputMouse(String deviceName, int vendorId, int productId,
+ String phys);
private static native int nativeOpenUinputTouchscreen(String deviceName, int vendorId,
- int productId, int height, int width);
+ int productId, String phys, int height, int width);
private static native boolean nativeCloseUinput(int fd);
private static native boolean nativeWriteKeyEvent(int fd, int androidKeyCode, int action);
private static native boolean nativeWriteButtonEvent(int fd, int buttonCode, int action);
@@ -345,20 +377,18 @@
/** Wrapper around the static native methods for tests. */
@VisibleForTesting
protected static class NativeWrapper {
- public int openUinputKeyboard(String deviceName, int vendorId, int productId) {
- return nativeOpenUinputKeyboard(deviceName, vendorId,
- productId);
+ public int openUinputKeyboard(String deviceName, int vendorId, int productId, String phys) {
+ return nativeOpenUinputKeyboard(deviceName, vendorId, productId, phys);
}
- public int openUinputMouse(String deviceName, int vendorId, int productId) {
- return nativeOpenUinputMouse(deviceName, vendorId,
- productId);
+ public int openUinputMouse(String deviceName, int vendorId, int productId, String phys) {
+ return nativeOpenUinputMouse(deviceName, vendorId, productId, phys);
}
- public int openUinputTouchscreen(String deviceName, int vendorId, int productId, int height,
- int width) {
- return nativeOpenUinputTouchscreen(deviceName, vendorId,
- productId, height, width);
+ public int openUinputTouchscreen(String deviceName, int vendorId,
+ int productId, String phys, int height, int width) {
+ return nativeOpenUinputTouchscreen(deviceName, vendorId, productId, phys, height,
+ width);
}
public boolean closeUinput(int fd) {
@@ -410,15 +440,17 @@
private final IBinder.DeathRecipient mDeathRecipient;
private final @Type int mType;
private final int mDisplayId;
+ private final String mPhys;
// Monotonically increasing number; devices with lower numbers were created earlier.
private final long mCreationOrderNumber;
- InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient,
- @Type int type, int displayId) {
+ InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, @Type int type,
+ int displayId, String phys) {
mFd = fd;
mDeathRecipient = deathRecipient;
mType = type;
mDisplayId = displayId;
+ mPhys = phys;
mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement();
}
@@ -445,6 +477,10 @@
public long getCreationOrderNumber() {
return mCreationOrderNumber;
}
+
+ public String getPhys() {
+ return mPhys;
+ }
}
private final class BinderDeathRecipient implements IBinder.DeathRecipient {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1106fe7..561009f 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -134,6 +134,7 @@
"app-compat-annotations",
"framework-tethering.stubs.module_lib",
"service-permission.stubs.system_server",
+ "service-supplementalprocess.stubs.system_server",
],
required: [
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 435d294..a35aa7c 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManager.ProcessState;
import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.content.ComponentName;
import android.content.LocusId;
@@ -375,10 +376,11 @@
* to this broadcast.
* @param timestampMs time (in millis) when the broadcast was dispatched, in
* {@link SystemClock#elapsedRealtime()} timebase.
+ * @param targetUidProcState process state of the uid that the broadcast is targeted to.
*/
public abstract void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage,
@NonNull UserHandle targetUser, long idForResponseEvent,
- @ElapsedRealtimeLong long timestampMs);
+ @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState);
/**
* Reports a notification posted event to the UsageStatsManager.
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index db510cb..7714dbc 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.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 @@
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 @@
} 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 @@
} 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/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index e5a7b4e..8a6b54f 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,7 +19,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
@@ -32,15 +31,21 @@
import android.os.UEventObserver;
import android.os.UserHandle;
import android.provider.Settings;
-import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
+import com.android.server.ExtconUEventObserver.ExtconInfo;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
* DockObserver monitors for a docking station.
@@ -48,9 +53,6 @@
final class DockObserver extends SystemService {
private static final String TAG = "DockObserver";
- private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
- private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
-
private static final int MSG_DOCK_STATE_CHANGED = 0;
private final PowerManager mPowerManager;
@@ -69,6 +71,92 @@
private final boolean mAllowTheaterModeWakeFromDock;
+ private final List<ExtconStateConfig> mExtconStateConfigs;
+
+ static final class ExtconStateProvider {
+ private final Map<String, String> mState;
+
+ ExtconStateProvider(Map<String, String> state) {
+ mState = state;
+ }
+
+ String getValue(String key) {
+ return mState.get(key);
+ }
+
+
+ static ExtconStateProvider fromString(String stateString) {
+ Map<String, String> states = new HashMap<>();
+ String[] lines = stateString.split("\n");
+ for (String line : lines) {
+ String[] fields = line.split("=");
+ if (fields.length == 2) {
+ states.put(fields[0], fields[1]);
+ } else {
+ Slog.e(TAG, "Invalid line: " + line);
+ }
+ }
+ return new ExtconStateProvider(states);
+ }
+
+ static ExtconStateProvider fromFile(String stateFilePath) {
+ char[] buffer = new char[1024];
+ try (FileReader file = new FileReader(stateFilePath)) {
+ int len = file.read(buffer, 0, 1024);
+ String stateString = (new String(buffer, 0, len)).trim();
+ return ExtconStateProvider.fromString(stateString);
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "No state file found at: " + stateFilePath);
+ return new ExtconStateProvider(new HashMap<>());
+ } catch (Exception e) {
+ Slog.e(TAG, "" , e);
+ return new ExtconStateProvider(new HashMap<>());
+ }
+ }
+ }
+
+ /**
+ * Represents a mapping from extcon state to EXTRA_DOCK_STATE value. Each
+ * instance corresponds to an entry in config_dockExtconStateMapping.
+ */
+ private static final class ExtconStateConfig {
+
+ // The EXTRA_DOCK_STATE that will be used if the extcon key-value pairs match
+ public final int extraStateValue;
+
+ // A list of key-value pairs that must be present in the extcon state for a match
+ // to be considered. An empty list is considered a matching wildcard.
+ public final List<Pair<String, String>> keyValuePairs = new ArrayList<>();
+
+ ExtconStateConfig(int extraStateValue) {
+ this.extraStateValue = extraStateValue;
+ }
+ }
+
+ private static List<ExtconStateConfig> loadExtconStateConfigs(Context context) {
+ String[] rows = context.getResources().getStringArray(
+ com.android.internal.R.array.config_dockExtconStateMapping);
+ try {
+ ArrayList<ExtconStateConfig> configs = new ArrayList<>();
+ for (String row : rows) {
+ String[] rowFields = row.split(",");
+ ExtconStateConfig config = new ExtconStateConfig(Integer.parseInt(rowFields[0]));
+ for (int i = 1; i < rowFields.length; i++) {
+ String[] keyValueFields = rowFields[i].split("=");
+ if (keyValueFields.length != 2) {
+ throw new IllegalArgumentException("Invalid key-value: " + rowFields[i]);
+ }
+ config.keyValuePairs.add(Pair.create(keyValueFields[0], keyValueFields[1]));
+ }
+ configs.add(config);
+ }
+ return configs;
+ } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
+ Slog.e(TAG, "Could not parse extcon state config", e);
+ return new ArrayList<>();
+ }
+ }
+
public DockObserver(Context context) {
super(context);
@@ -77,9 +165,25 @@
mAllowTheaterModeWakeFromDock = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
- init(); // set initial status
+ mExtconStateConfigs = loadExtconStateConfigs(context);
- mObserver.startObserving(DOCK_UEVENT_MATCH);
+ List<ExtconInfo> infos = ExtconInfo.getExtconInfoForTypes(new String[] {
+ ExtconInfo.EXTCON_DOCK
+ });
+
+ if (!infos.isEmpty()) {
+ ExtconInfo info = infos.get(0);
+ Slog.i(TAG, "Found extcon info devPath: " + info.getDevicePath()
+ + ", statePath: " + info.getStatePath());
+
+ // set initial status
+ setDockStateFromProviderLocked(ExtconStateProvider.fromFile(info.getStatePath()));
+ mPreviousDockState = mActualDockState;
+
+ mExtconUEventObserver.startObserving(info);
+ } else {
+ Slog.i(TAG, "No extcon dock device found in this kernel.");
+ }
}
@Override
@@ -101,26 +205,6 @@
}
}
- private void init() {
- synchronized (mLock) {
- try {
- char[] buffer = new char[1024];
- FileReader file = new FileReader(DOCK_STATE_PATH);
- try {
- int len = file.read(buffer, 0, 1024);
- setActualDockStateLocked(Integer.parseInt((new String(buffer, 0, len)).trim()));
- mPreviousDockState = mActualDockState;
- } finally {
- file.close();
- }
- } catch (FileNotFoundException e) {
- Slog.w(TAG, "This kernel does not have dock station support");
- } catch (Exception e) {
- Slog.e(TAG, "" , e);
- }
- }
- }
-
private void setActualDockStateLocked(int newState) {
mActualDockState = newState;
if (!mUpdatesStopped) {
@@ -234,19 +318,50 @@
}
};
- private final UEventObserver mObserver = new UEventObserver() {
- @Override
- public void onUEvent(UEventObserver.UEvent event) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Slog.v(TAG, "Dock UEVENT: " + event.toString());
+ private int getDockedStateExtraValue(ExtconStateProvider state) {
+ for (ExtconStateConfig config : mExtconStateConfigs) {
+ boolean match = true;
+ for (Pair<String, String> keyValue : config.keyValuePairs) {
+ String stateValue = state.getValue(keyValue.first);
+ match = match && keyValue.second.equals(stateValue);
+ if (!match) {
+ break;
+ }
}
- try {
- synchronized (mLock) {
- setActualDockStateLocked(Integer.parseInt(event.get("SWITCH_STATE")));
+ if (match) {
+ return config.extraStateValue;
+ }
+ }
+
+ return Intent.EXTRA_DOCK_STATE_DESK;
+ }
+
+ @VisibleForTesting
+ void setDockStateFromProviderForTesting(ExtconStateProvider provider) {
+ synchronized (mLock) {
+ setDockStateFromProviderLocked(provider);
+ }
+ }
+
+ private void setDockStateFromProviderLocked(ExtconStateProvider provider) {
+ int state = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ if ("1".equals(provider.getValue("DOCK"))) {
+ state = getDockedStateExtraValue(provider);
+ }
+ setActualDockStateLocked(state);
+ }
+
+ private final ExtconUEventObserver mExtconUEventObserver = new ExtconUEventObserver() {
+ @Override
+ public void onUEvent(ExtconInfo extconInfo, UEventObserver.UEvent event) {
+ synchronized (mLock) {
+ String stateString = event.get("STATE");
+ if (stateString != null) {
+ setDockStateFromProviderLocked(ExtconStateProvider.fromString(stateString));
+ } else {
+ Slog.e(TAG, "Extcon event missing STATE: " + event);
}
- } catch (NumberFormatException e) {
- Slog.e(TAG, "Could not parse switch state from event " + event);
}
}
};
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 0bc3fcc..ce30f03 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -576,19 +576,20 @@
return () -> {
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(oldTrace);
final String serviceName = service.getClass().getName();
+ final int curUserId = curUser.getUserIdentifier();
+ t.traceBegin("ssm.on" + USER_STARTING + "User-" + curUserId + "_" + serviceName);
try {
- final int curUserId = curUser.getUserIdentifier();
- t.traceBegin("ssm.on" + USER_STARTING + "User-" + curUserId + "_" + serviceName);
long time = SystemClock.elapsedRealtime();
service.onUserStarting(curUser);
warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
"on" + USER_STARTING + "User-" + curUserId);
- t.traceEnd();
} catch (Exception e) {
Slog.wtf(TAG, "Failure reporting " + USER_STARTING + " of user " + curUser
+ " to service " + serviceName, e);
Slog.e(TAG, "Disabling thread pool - please capture a bug report.");
sUseLifecycleThreadPool = false;
+ } finally {
+ t.traceEnd();
}
};
}
@@ -601,11 +602,18 @@
final int curUserId = curUser.getUserIdentifier();
t.traceBegin("ssm.on" + USER_COMPLETED_EVENT + "User-" + curUserId
+ "_" + eventType + "_" + serviceName);
- long time = SystemClock.elapsedRealtime();
- service.onUserCompletedEvent(curUser, eventType);
- warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
- "on" + USER_COMPLETED_EVENT + "User-" + curUserId);
- t.traceEnd();
+ try {
+ long time = SystemClock.elapsedRealtime();
+ service.onUserCompletedEvent(curUser, eventType);
+ warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
+ "on" + USER_COMPLETED_EVENT + "User-" + curUserId);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Failure reporting " + USER_COMPLETED_EVENT + " of user " + curUser
+ + " to service " + serviceName, e);
+ throw e;
+ } finally {
+ t.traceEnd();
+ }
};
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b5c0a67..9353dd8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2721,7 +2721,8 @@
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, final IServiceConnection connection, int flags,
- String instanceName, String callingPackage, final int userId)
+ String instanceName, boolean isSupplementalProcessService, String callingPackage,
+ final int userId)
throws TransactionTooLargeException {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
+ " type=" + resolvedType + " conn=" + connection.asBinder()
@@ -2805,10 +2806,9 @@
final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0;
final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
- ServiceLookupResult res =
- retrieveServiceLocked(service, instanceName, resolvedType, callingPackage,
- callingPid, callingUid, userId, true,
- callerFg, isBindExternal, allowInstant);
+ ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
+ isSupplementalProcessService, resolvedType, callingPackage, callingPid, callingUid,
+ userId, true, callerFg, isBindExternal, allowInstant);
if (res == null) {
return 0;
}
@@ -3228,6 +3228,20 @@
int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
boolean allowInstant) {
+ return retrieveServiceLocked(service, instanceName, false, resolvedType, callingPackage,
+ callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal,
+ allowInstant);
+ }
+
+ private ServiceLookupResult retrieveServiceLocked(Intent service,
+ String instanceName, boolean isSupplementalProcessService, String resolvedType,
+ String callingPackage, int callingPid, int callingUid, int userId,
+ boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
+ boolean allowInstant) {
+ if (isSupplementalProcessService && instanceName == null) {
+ throw new IllegalArgumentException("No instanceName provided for supplemental process");
+ }
+
ServiceRecord r = null;
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service
+ " type=" + resolvedType + " callingUid=" + callingUid);
@@ -3249,7 +3263,6 @@
if (instanceName == null) {
comp = service.getComponent();
} else {
- // This is for isolated services
final ComponentName realComp = service.getComponent();
if (realComp == null) {
throw new IllegalArgumentException("Can't use custom instance name '" + instanceName
@@ -3304,12 +3317,19 @@
return null;
}
if (instanceName != null
- && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
+ && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0
+ && !isSupplementalProcessService) {
throw new IllegalArgumentException("Can't use instance name '" + instanceName
- + "' with non-isolated service '" + sInfo.name + "'");
+ + "' with non-isolated non-supplemental service '" + sInfo.name + "'");
}
- ComponentName className = new ComponentName(
- sInfo.applicationInfo.packageName, sInfo.name);
+ if (isSupplementalProcessService
+ && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
+ throw new IllegalArgumentException("Service cannot be both supplemental and "
+ + "isolated");
+ }
+
+ ComponentName className = new ComponentName(sInfo.applicationInfo.packageName,
+ sInfo.name);
ComponentName name = comp != null ? comp : className;
if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid,
name.getPackageName(), sInfo.applicationInfo.uid)) {
@@ -3392,7 +3412,8 @@
= new Intent.FilterComparison(service.cloneFilter());
final ServiceRestarter res = new ServiceRestarter();
r = new ServiceRecord(mAm, className, name, definingPackageName,
- definingUid, filter, sInfo, callingFromFg, res);
+ definingUid, filter, sInfo, callingFromFg, res,
+ isSupplementalProcessService);
res.setService(r);
smap.mServicesByInstanceName.put(name, r);
smap.mServicesByIntent.put(filter, r);
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 9a1bfdd..d9ee7d9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -18,6 +18,9 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.TransactionTooLargeException;
/**
* Interface for in-process calls into
@@ -58,4 +61,24 @@
* @hide
*/
void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs);
+
+ /**
+ * Starts a supplemental process service and binds to it. You can through the arguments here
+ * have the system bring up multiple concurrent processes hosting their own instance of that
+ * service. The <var>userAppUid</var> you provide here identifies the different instances - each
+ * unique uid is attributed to a supplemental process.
+ *
+ * @param service Identifies the supplemental process service to connect to. The Intent must
+ * specify an explicit component name. This value cannot be null.
+ * @param conn Receives information as the service is started and stopped.
+ * This must be a valid ServiceConnection object; it must not be null.
+ * @param userAppUid Uid of the app for which the supplemental process needs to be spawned.
+ * @return {@code true} if the system is in the process of bringing up a
+ * service that your client has permission to bind to; {@code false}
+ * if the system couldn't find the service or if your client doesn't
+ * have permission to bind to it.
+ */
+ boolean startAndBindSupplementalProcessService(@NonNull Intent service,
+ @NonNull ServiceConnection conn, int userAppUid) throws TransactionTooLargeException;
+
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 902659c..fafe908 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -218,6 +218,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.LocusId;
+import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityPresentationInfo;
import android.content.pm.ApplicationInfo;
@@ -256,6 +257,7 @@
import android.os.BugreportParams;
import android.os.Build;
import android.os.Bundle;
+import android.os.ConditionVariable;
import android.os.Debug;
import android.os.DropBoxManager;
import android.os.FactoryTest;
@@ -336,7 +338,7 @@
import com.android.internal.app.SystemUserHomeActivity;
import com.android.internal.app.procstats.ProcessState;
import com.android.internal.app.procstats.ProcessStats;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BackgroundThread;
@@ -457,7 +459,7 @@
* 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;
@@ -2899,16 +2901,31 @@
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)) {
@@ -4951,7 +4968,7 @@
// This line is needed to CTS test for the correct exception handling
// See b/138952436#comment36 for context
Slog.i(TAG, "About to commit checkpoint");
- IStorageManager storageManager = PackageHelper.getStorageManager();
+ IStorageManager storageManager = InstallLocationUtils.getStorageManager();
storageManager.commitChanges();
} catch (Exception e) {
PowerManager pm = (PowerManager)
@@ -12314,13 +12331,25 @@
public int bindService(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, IServiceConnection connection, int flags,
String callingPackage, int userId) throws TransactionTooLargeException {
- return bindIsolatedService(caller, token, service, resolvedType, connection, flags,
+ return bindServiceInstance(caller, token, service, resolvedType, connection, flags,
null, callingPackage, userId);
}
- public int bindIsolatedService(IApplicationThread caller, IBinder token, Intent service,
+ /**
+ * Binds to a service with a given instanceName, creating it if it does not already exist.
+ * If the instanceName field is not supplied, binding to the service occurs as usual.
+ */
+ public int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, IServiceConnection connection, int flags, String instanceName,
String callingPackage, int userId) throws TransactionTooLargeException {
+ return bindServiceInstance(caller, token, service, resolvedType, connection, flags,
+ instanceName, false, callingPackage, userId);
+ }
+
+ private int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service,
+ String resolvedType, IServiceConnection connection, int flags, String instanceName,
+ boolean isSupplementalProcessService, String callingPackage, int userId)
+ throws TransactionTooLargeException {
enforceNotIsolatedCaller("bindService");
// Refuse possible leaked file descriptors
@@ -12332,6 +12361,10 @@
throw new IllegalArgumentException("callingPackage cannot be null");
}
+ if (isSupplementalProcessService && instanceName == null) {
+ throw new IllegalArgumentException("No instance name provided for isolated process");
+ }
+
// Ensure that instanceName, which is caller provided, does not contain
// unusual characters.
if (instanceName != null) {
@@ -12345,8 +12378,8 @@
}
synchronized(this) {
- return mServices.bindServiceLocked(caller, token, service,
- resolvedType, connection, flags, instanceName, callingPackage, userId);
+ return mServices.bindServiceLocked(caller, token, service, resolvedType, connection,
+ flags, instanceName, isSupplementalProcessService, callingPackage, userId);
}
}
@@ -12765,7 +12798,7 @@
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
@@ -12839,6 +12872,8 @@
// 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;
}
}
@@ -13331,6 +13366,13 @@
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,
@@ -15478,6 +15520,62 @@
}
}
+ /**
+ * Dump the resources structure for the given process
+ *
+ * @param process The process to dump resource info for
+ * @param fd The FileDescriptor to dump it into
+ * @throws RemoteException
+ */
+ public boolean dumpResources(String process, ParcelFileDescriptor fd, RemoteCallback callback)
+ throws RemoteException {
+ synchronized (this) {
+ ProcessRecord proc = findProcessLOSP(process, UserHandle.USER_CURRENT, "dumpResources");
+ IApplicationThread thread;
+ if (proc == null || (thread = proc.getThread()) == null) {
+ throw new IllegalArgumentException("Unknown process: " + process);
+ }
+ thread.dumpResources(fd, callback);
+ return true;
+ }
+ }
+
+ /**
+ * Dump the resources structure for all processes
+ *
+ * @param fd The FileDescriptor to dump it into
+ * @throws RemoteException
+ */
+ public void dumpAllResources(ParcelFileDescriptor fd, PrintWriter pw) throws RemoteException {
+ synchronized (mProcLock) {
+ mProcessList.forEachLruProcessesLOSP(true, app -> {
+ ConditionVariable lock = new ConditionVariable();
+ RemoteCallback
+ finishCallback = new RemoteCallback(result -> lock.open(), null);
+
+ pw.println(String.format("------ DUMP RESOURCES %s (%s) ------",
+ app.processName,
+ app.info.packageName));
+ pw.flush();
+ try {
+ app.getThread().dumpResources(fd.dup(), finishCallback);
+ lock.block(2000);
+ } catch (Exception e) {
+ pw.println(String.format(
+ "------ EXCEPTION DUMPING RESOURCES for %s (%s): %s ------",
+ app.processName,
+ app.info.packageName,
+ e.getMessage()));
+ pw.flush();
+ }
+ pw.println(String.format("------ END DUMP RESOURCES %s (%s) ------",
+ app.processName,
+ app.info.packageName));
+ pw.flush();
+ });
+ }
+ }
+
@Override
public void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize,
String reportPackage) {
@@ -15823,6 +15921,34 @@
}
@Override
+ public boolean startAndBindSupplementalProcessService(Intent service,
+ ServiceConnection conn, int userAppUid) throws TransactionTooLargeException {
+ if (service == null) {
+ throw new IllegalArgumentException("intent is null");
+ }
+ if (conn == null) {
+ throw new IllegalArgumentException("connection is null");
+ }
+ if (service.getComponent() == null) {
+ throw new IllegalArgumentException("service must specify explicit component");
+ }
+ if (!UserHandle.isApp(userAppUid)) {
+ throw new IllegalArgumentException("uid is not within application range");
+ }
+
+ Handler handler = mContext.getMainThreadHandler();
+ int flags = Context.BIND_AUTO_CREATE;
+
+ final IServiceConnection sd = mContext.getServiceDispatcher(conn, handler, flags);
+ service.prepareToLeaveProcess(mContext);
+ return ActivityManagerService.this.bindServiceInstance(
+ mContext.getIApplicationThread(), mContext.getActivityToken(), service,
+ service.resolveTypeIfNeeded(mContext.getContentResolver()), sd, flags,
+ Integer.toString(userAppUid), /*isSupplementalProcessService*/ true,
+ mContext.getOpPackageName(), UserHandle.getUserId(userAppUid)) != 0;
+ }
+
+ @Override
public void onUserRemoved(@UserIdInt int userId) {
// Clean up any ActivityTaskManager state (by telling it the user is stopped)
mAtmInternal.onUserStopped(userId);
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 1315293..465623f 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_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_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.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.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 @@
@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 @@
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
ActivityThread.currentApplication().getMainExecutor(), mConstantsObserver);
mConstantsObserver.start();
+ initBgRestrictionExemptioFromSysConfig();
initRestrictionStates();
initSystemModuleNames();
registerForUidObservers();
@@ -711,6 +734,22 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
private AppBatteryExemptionTracker mAppBatteryExemptionTracker;
private AppFGSTracker mAppFGSTracker;
private AppMediaSessionTracker mAppMediaSessionTracker;
+ private TelephonyManager mTelephonyManager;
Injector(Context context) {
mContext = context;
@@ -1890,6 +1962,13 @@
return mRoleManager;
}
+ TelephonyManager getTelephonyManager() {
+ if (mTelephonyManager == null) {
+ mTelephonyManager = getContext().getSystemService(TelephonyManager.class);
+ }
+ return mTelephonyManager;
+ }
+
AppFGSTracker getAppFGSTracker() {
return mAppFGSTracker;
}
@@ -1939,6 +2018,19 @@
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 @@
};
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/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c8ad0e8..5da461d 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1365,7 +1365,7 @@
}
public void notePhoneDataConnectionState(final int dataType, final boolean hasData,
- final int serviceType) {
+ final int serviceType, final int nrFrequency) {
enforceCallingPermission();
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1373,7 +1373,7 @@
mHandler.post(() -> {
synchronized (mStats) {
mStats.notePhoneDataConnectionStateLocked(dataType, hasData, serviceType,
- elapsedRealtime, uptime);
+ nrFrequency, elapsedRealtime, uptime);
}
});
}
@@ -1962,6 +1962,32 @@
}
}
+ /**
+ * Bluetooth on stat logging
+ */
+ public void noteBluetoothOn(int uid, int reason, String packageName) {
+ if (Binder.getCallingPid() != Process.myPid()) {
+ mContext.enforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT,
+ Binder.getCallingPid(), uid, null);
+ }
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
+ uid, null, FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED,
+ reason, packageName);
+ }
+
+ /**
+ * Bluetooth off stat logging
+ */
+ public void noteBluetoothOff(int uid, int reason, String packageName) {
+ if (Binder.getCallingPid() != Process.myPid()) {
+ mContext.enforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT,
+ Binder.getCallingPid(), uid, null);
+ }
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
+ uid, null, FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED,
+ reason, packageName);
+ }
+
@Override
public void noteBleScanStarted(final WorkSource ws, final boolean isUnoptimized) {
enforceCallingPermission();
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 0c383eb..8ad6260 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -334,7 +334,7 @@
mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
// Tell the application to launch this receiver.
- maybeReportBroadcastDispatchedEventLocked(r);
+ maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
r.intent.setComponent(r.curComponent);
boolean started = false;
@@ -870,7 +870,7 @@
+ " due to receiver " + filter.receiverList.app
+ " (uid " + filter.receiverList.uid + ")"
+ " not specifying RECEIVER_EXPORTED");
- // skip = true;
+ skip = true;
}
if (skip) {
@@ -927,7 +927,7 @@
r.receiverTime = SystemClock.uptimeMillis();
maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
- maybeReportBroadcastDispatchedEventLocked(r);
+ maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
new Intent(r.intent), r.resultCode, r.resultData,
r.resultExtras, r.ordered, r.initialSticky, r.userId);
@@ -1856,7 +1856,7 @@
return null;
}
- private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r) {
+ private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) {
final String targetPackage = getTargetPackage(r);
// Ignore non-explicit broadcasts
if (targetPackage == null) {
@@ -1867,11 +1867,10 @@
if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
return;
}
- // TODO (206518114): Only report this event when the broadcast is dispatched while the app
- // is in the background state.
getUsageStatsManagerInternal().reportBroadcastDispatched(
r.callingUid, targetPackage, UserHandle.of(r.userId),
- r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime());
+ r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
+ mService.getUidStateLocked(targetUid));
}
@NonNull
diff --git a/services/core/java/com/android/server/am/DataConnectionStats.java b/services/core/java/com/android/server/am/DataConnectionStats.java
index 6e39a4c..f0910dc 100644
--- a/services/core/java/com/android/server/am/DataConnectionStats.java
+++ b/services/core/java/com/android/server/am/DataConnectionStats.java
@@ -109,7 +109,7 @@
}
try {
mBatteryStats.notePhoneDataConnectionState(networkType, visible,
- mServiceState.getState());
+ mServiceState.getState(), mServiceState.getNrFrequencyRange());
} catch (RemoteException e) {
Log.w(TAG, "Error noting data connection state", e);
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 24e7ba4..da78e2d 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -570,6 +570,14 @@
ComponentName instanceName, String definingPackageName, int definingUid,
Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
Runnable restarter) {
+ this(ams, name, instanceName, definingPackageName, definingUid, intent, sInfo, callerIsFg,
+ restarter, false);
+ }
+
+ ServiceRecord(ActivityManagerService ams, ComponentName name,
+ ComponentName instanceName, String definingPackageName, int definingUid,
+ Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
+ Runnable restarter, boolean isSupplementalProcessService) {
this.ams = ams;
this.name = name;
this.instanceName = instanceName;
@@ -580,7 +588,8 @@
serviceInfo = sInfo;
appInfo = sInfo.applicationInfo;
packageName = sInfo.applicationInfo.packageName;
- if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
+ if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
+ || isSupplementalProcessService) {
processName = sInfo.processName + ":" + instanceName.getClassName();
} else {
processName = sInfo.processName;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 3c9d29d..551773e 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -20,6 +20,11 @@
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static com.android.internal.R.styleable.GameModeConfig_allowGameAngleDriver;
+import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscaling;
+import static com.android.internal.R.styleable.GameModeConfig_allowGameFpsOverride;
+import static com.android.internal.R.styleable.GameModeConfig_supportsBatteryGameMode;
+import static com.android.internal.R.styleable.GameModeConfig_supportsPerformanceGameMode;
import static com.android.server.wm.CompatModePackages.DOWNSCALED;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_30;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_35;
@@ -54,6 +59,10 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
import android.hardware.power.Mode;
import android.net.Uri;
import android.os.Binder;
@@ -71,8 +80,10 @@
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.util.ArrayMap;
+import android.util.AttributeSet;
import android.util.KeyValueListParser;
import android.util.Slog;
+import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -84,7 +95,11 @@
import com.android.server.SystemService;
import com.android.server.SystemService.TargetUser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.FileDescriptor;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
@@ -425,12 +440,20 @@
public static final String METADATA_BATTERY_MODE_ENABLE =
"com.android.app.gamemode.battery.enabled";
+ /**
+ * Metadata that allows a game to specify all intervention information with an XML file in
+ * the application field.
+ */
+ public static final String METADATA_GAME_MODE_CONFIG = "android.game_mode_config";
+
+ private static final String GAME_MODE_CONFIG_NODE_NAME = "game-mode-config";
private final String mPackageName;
private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs;
private boolean mPerfModeOptedIn;
private boolean mBatteryModeOptedIn;
private boolean mAllowDownscale;
private boolean mAllowAngle;
+ private boolean mAllowFpsOverride;
GamePackageConfiguration(String packageName, int userId) {
mPackageName = packageName;
@@ -438,18 +461,21 @@
try {
final ApplicationInfo ai = mPackageManager.getApplicationInfoAsUser(packageName,
PackageManager.GET_META_DATA, userId);
- if (ai.metaData != null) {
- mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
- mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
- mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
- mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
- } else {
- mPerfModeOptedIn = false;
- mBatteryModeOptedIn = false;
- mAllowDownscale = true;
- mAllowAngle = true;
+ if (!parseInterventionFromXml(ai, packageName)) {
+ if (ai.metaData != null) {
+ mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
+ mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
+ mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
+ mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
+ } else {
+ mPerfModeOptedIn = false;
+ mBatteryModeOptedIn = false;
+ mAllowDownscale = true;
+ mAllowAngle = true;
+ mAllowFpsOverride = true;
+ }
}
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (NameNotFoundException e) {
// Not all packages are installed, hence ignore those that are not installed yet.
Slog.v(TAG, "Failed to get package metadata");
}
@@ -469,6 +495,53 @@
}
}
+ private boolean parseInterventionFromXml(ApplicationInfo ai, String packageName) {
+ boolean xmlFound = false;
+ try (XmlResourceParser parser = ai.loadXmlMetaData(mPackageManager,
+ METADATA_GAME_MODE_CONFIG)) {
+ if (parser == null) {
+ Slog.v(TAG, "No " + METADATA_GAME_MODE_CONFIG
+ + " meta-data found for package " + mPackageName);
+ } else {
+ xmlFound = true;
+ final Resources resources = mPackageManager.getResourcesForApplication(
+ packageName);
+ final AttributeSet attributeSet = Xml.asAttributeSet(parser);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // Do nothing
+ }
+
+ boolean isStartingTagGameModeConfig =
+ GAME_MODE_CONFIG_NODE_NAME.equals(parser.getName());
+ if (!isStartingTagGameModeConfig) {
+ Slog.w(TAG, "Meta-data does not start with "
+ + GAME_MODE_CONFIG_NODE_NAME
+ + " tag");
+ } else {
+ final TypedArray array = resources.obtainAttributes(attributeSet,
+ com.android.internal.R.styleable.GameModeConfig);
+ mPerfModeOptedIn = array.getBoolean(
+ GameModeConfig_supportsPerformanceGameMode, false);
+ mBatteryModeOptedIn = array.getBoolean(
+ GameModeConfig_supportsBatteryGameMode,
+ false);
+ mAllowDownscale = array.getBoolean(GameModeConfig_allowGameDownscaling,
+ true);
+ mAllowAngle = array.getBoolean(GameModeConfig_allowGameAngleDriver, true);
+ mAllowFpsOverride = array.getBoolean(GameModeConfig_allowGameFpsOverride,
+ true);
+ array.recycle();
+ }
+ }
+ } catch (NameNotFoundException | XmlPullParserException | IOException ex) {
+ Slog.e(TAG, "Error while parsing XML meta-data for "
+ + METADATA_GAME_MODE_CONFIG);
+ }
+ return xmlFound;
+ }
+
/**
* GameModeConfiguration contains all the values for all the interventions associated with
* a game mode.
@@ -497,7 +570,8 @@
mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING);
- mFps = parser.getString(FPS_KEY, DEFAULT_FPS);
+ mFps = mAllowFpsOverride && !willGamePerformOptimizations(mGameMode)
+ ? parser.getString(FPS_KEY, DEFAULT_FPS) : DEFAULT_FPS;
// We only want to use ANGLE if:
// - We're allowed to use ANGLE (the app hasn't opted out via the manifest) AND
// - The app has not opted in to performing the work itself AND
@@ -691,7 +765,7 @@
try {
return mPackageManager.getPackageUidAsUser(packageName, userId)
== Binder.getCallingUid();
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (NameNotFoundException e) {
return false;
}
}
@@ -855,7 +929,7 @@
*/
@Override
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
- public @GameMode boolean getAngleEnabled(String packageName, int userId)
+ public @GameMode boolean isAngleEnabled(String packageName, int userId)
throws SecurityException {
final int gameMode = getGameMode(packageName, userId);
if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
@@ -1413,7 +1487,7 @@
if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
return;
}
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (NameNotFoundException e) {
// Ignore the exception.
}
switch (intent.getAction()) {
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 9136219..960fbf1 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -50,6 +50,7 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
import com.android.server.wm.WindowManagerService;
import java.util.List;
@@ -62,6 +63,18 @@
private static final int CREATE_GAME_SESSION_TIMEOUT_MS = 10_000;
private static final boolean DEBUG = false;
+ private final TaskSystemBarsListener mTaskSystemBarsVisibilityListener =
+ new TaskSystemBarsListener() {
+ @Override
+ public void onTransientSystemBarsVisibilityChanged(
+ int taskId,
+ boolean visible,
+ boolean wereRevealedFromSwipeOnSystemBar) {
+ GameServiceProviderInstanceImpl.this.onTransientSystemBarsVisibilityChanged(
+ taskId, visible, wereRevealedFromSwipeOnSystemBar);
+ }
+ };
+
private final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
@@ -98,7 +111,10 @@
private final IGameServiceController mGameServiceController =
new IGameServiceController.Stub() {
@Override
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY)
public void createGameSession(int taskId) {
+ mContext.enforceCallingPermission(Manifest.permission.MANAGE_GAME_ACTIVITY,
+ "createGameSession()");
mBackgroundExecutor.execute(() -> {
GameServiceProviderInstanceImpl.this.createGameSession(taskId);
});
@@ -116,9 +132,10 @@
});
}
- @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+ @Override
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY)
public void restartGame(int taskId) {
- mContext.enforceCallingPermission(Manifest.permission.FORCE_STOP_PACKAGES,
+ mContext.enforceCallingPermission(Manifest.permission.MANAGE_GAME_ACTIVITY,
"restartGame()");
mBackgroundExecutor.execute(() -> {
GameServiceProviderInstanceImpl.this.restartGame(taskId);
@@ -199,6 +216,8 @@
} catch (RemoteException e) {
Slog.w(TAG, "Failed to register task stack listener", e);
}
+
+ mWindowManagerInternal.registerTaskSystemBarsListener(mTaskSystemBarsVisibilityListener);
}
@GuardedBy("mLock")
@@ -214,6 +233,9 @@
Slog.w(TAG, "Failed to unregister task stack listener", e);
}
+ mWindowManagerInternal.unregisterTaskSystemBarsListener(
+ mTaskSystemBarsVisibilityListener);
+
for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
destroyGameSessionFromRecord(gameSessionRecord);
}
@@ -303,6 +325,37 @@
}
}
+ private void onTransientSystemBarsVisibilityChanged(
+ int taskId,
+ boolean visible,
+ boolean wereRevealedFromSwipeOnSystemBar) {
+ if (visible && !wereRevealedFromSwipeOnSystemBar) {
+ return;
+ }
+
+ GameSessionRecord gameSessionRecord;
+ synchronized (mLock) {
+ gameSessionRecord = mGameSessions.get(taskId);
+ }
+
+ if (gameSessionRecord == null) {
+ return;
+ }
+
+ IGameSession gameSession = gameSessionRecord.getGameSession();
+ if (gameSession == null) {
+ return;
+ }
+
+ try {
+ gameSession.onTransientSystemBarVisibilityFromRevealGestureChanged(visible);
+ } catch (RemoteException ex) {
+ Slog.w(TAG,
+ "Failed to send transient system bars visibility from reveal gesture for task: "
+ + taskId);
+ }
+ }
+
private void createGameSession(int taskId) {
synchronized (mLock) {
createGameSessionLocked(taskId);
@@ -372,12 +425,12 @@
}, mBackgroundExecutor);
AndroidFuture<Void> unusedPostCreateGameSessionFuture =
- mGameSessionServiceConnector.post(gameService -> {
+ mGameSessionServiceConnector.post(gameSessionService -> {
CreateGameSessionRequest createGameSessionRequest =
new CreateGameSessionRequest(
taskId,
existingGameSessionRecord.getComponentName().getPackageName());
- gameService.create(
+ gameSessionService.create(
mGameSessionController,
createGameSessionRequest,
gameSessionViewHostConfiguration,
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index a139589..d2fa386 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -22,6 +22,7 @@
import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
import static android.service.attention.AttentionService.ATTENTION_FAILURE_CANCELLED;
import static android.service.attention.AttentionService.ATTENTION_FAILURE_UNKNOWN;
+import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
import android.Manifest;
import android.annotation.NonNull;
@@ -29,6 +30,7 @@
import android.app.ActivityThread;
import android.attention.AttentionManagerInternal;
import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
+import android.attention.AttentionManagerInternal.ProximityCallbackInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -57,6 +59,7 @@
import android.service.attention.AttentionService.AttentionSuccessCodes;
import android.service.attention.IAttentionCallback;
import android.service.attention.IAttentionService;
+import android.service.attention.IProximityCallback;
import android.text.TextUtils;
import android.util.Slog;
@@ -134,6 +137,15 @@
@GuardedBy("mLock")
AttentionCheck mCurrentAttentionCheck;
+ /**
+ * A proxy for relaying proximity information between the Attention Service and the client.
+ * The proxy will be initialized when the client calls onStartProximityUpdates and will be
+ * disabled only when the client calls onStopProximityUpdates.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ ProximityUpdate mCurrentProximityUpdate;
+
public AttentionManagerService(Context context) {
this(context, (PowerManager) context.getSystemService(Context.POWER_SERVICE),
new Object(), null);
@@ -315,6 +327,77 @@
}
}
+ /**
+ * Requests the continuous updates of proximity signal via the provided callback,
+ * until the given callback is stopped.
+ *
+ * Calling this multiple times for duplicate requests will be no-ops, returning true.
+ *
+ * @return {@code true} if the framework was able to dispatch the request
+ */
+ @VisibleForTesting
+ boolean onStartProximityUpdates(ProximityCallbackInternal callbackInternal) {
+ Objects.requireNonNull(callbackInternal);
+ if (!mIsServiceEnabled) {
+ Slog.w(LOG_TAG, "Trying to call onProximityUpdate() on an unsupported device.");
+ return false;
+ }
+
+ if (!isServiceAvailable()) {
+ Slog.w(LOG_TAG, "Service is not available at this moment.");
+ return false;
+ }
+
+ // don't allow proximity request in screen off state.
+ // This behavior might change in the future.
+ if (!mPowerManager.isInteractive()) {
+ Slog.w(LOG_TAG, "Proximity Service is unavailable during screen off at this moment.");
+ return false;
+ }
+
+ synchronized (mLock) {
+ // schedule shutting down the connection if no one resets this timer
+ freeIfInactiveLocked();
+
+ // lazily start the service, which should be very lightweight to start
+ bindLocked();
+
+ /*
+ Prevent spamming with multiple requests, only one at a time is allowed.
+ If there are use-cases for keeping track of multiple requests, we
+ can refactor ProximityUpdate object to keep track of multiple internal callbacks.
+ */
+ if (mCurrentProximityUpdate != null && mCurrentProximityUpdate.mStartedUpdates) {
+ if (mCurrentProximityUpdate.mCallbackInternal == callbackInternal) {
+ Slog.w(LOG_TAG, "Provided callback is already registered. Skipping.");
+ return true;
+ } else {
+ // reject the new request since the old request is still alive.
+ Slog.w(LOG_TAG, "New proximity update cannot be processed because there is "
+ + "already an ongoing update");
+ return false;
+ }
+ }
+ mCurrentProximityUpdate = new ProximityUpdate(callbackInternal);
+ return mCurrentProximityUpdate.startUpdates();
+ }
+ }
+
+ /** Cancels the specified proximity registration. */
+ @VisibleForTesting
+ void onStopProximityUpdates(ProximityCallbackInternal callbackInternal) {
+ synchronized (mLock) {
+ if (mCurrentProximityUpdate == null
+ || !mCurrentProximityUpdate.mCallbackInternal.equals(callbackInternal)
+ || !mCurrentProximityUpdate.mStartedUpdates) {
+ Slog.w(LOG_TAG, "Cannot stop a non-current callback");
+ return;
+ }
+ mCurrentProximityUpdate.cancelUpdates();
+ mCurrentProximityUpdate = null;
+ }
+ }
+
@GuardedBy("mLock")
@VisibleForTesting
protected void freeIfInactiveLocked() {
@@ -390,15 +473,18 @@
ipw.println("Class=" + mComponentName.getClassName());
ipw.decreaseIndent();
}
- ipw.println("binding=" + mBinding);
- ipw.println("current attention check:");
synchronized (mLock) {
+ ipw.println("binding=" + mBinding);
+ ipw.println("current attention check:");
if (mCurrentAttentionCheck != null) {
mCurrentAttentionCheck.dump(ipw);
}
if (mAttentionCheckCacheBuffer != null) {
mAttentionCheckCacheBuffer.dump(ipw);
}
+ if (mCurrentProximityUpdate != null) {
+ mCurrentProximityUpdate.dump(ipw);
+ }
}
}
@@ -417,6 +503,17 @@
public void cancelAttentionCheck(AttentionCallbackInternal callbackInternal) {
AttentionManagerService.this.cancelAttentionCheck(callbackInternal);
}
+
+ @Override
+ public boolean onStartProximityUpdates(
+ ProximityCallbackInternal callback) {
+ return AttentionManagerService.this.onStartProximityUpdates(callback);
+ }
+
+ @Override
+ public void onStopProximityUpdates(ProximityCallbackInternal callback) {
+ AttentionManagerService.this.onStopProximityUpdates(callback);
+ }
}
@VisibleForTesting
@@ -536,6 +633,71 @@
}
}
+ @VisibleForTesting
+ final class ProximityUpdate {
+ private final ProximityCallbackInternal mCallbackInternal;
+ private final IProximityCallback mIProximityCallback;
+ private boolean mStartedUpdates;
+
+ ProximityUpdate(ProximityCallbackInternal callbackInternal) {
+ mCallbackInternal = callbackInternal;
+ mIProximityCallback = new IProximityCallback.Stub() {
+ @Override
+ public void onProximityUpdate(double distance) {
+ synchronized (mLock) {
+ mCallbackInternal.onProximityUpdate(distance);
+ freeIfInactiveLocked();
+ }
+ }
+ };
+ }
+
+ boolean startUpdates() {
+ synchronized (mLock) {
+ if (mStartedUpdates) {
+ Slog.w(LOG_TAG, "Already registered to a proximity service.");
+ return false;
+ }
+ if (mService == null) {
+ Slog.w(LOG_TAG,
+ "There is no service bound. Proximity update request rejected.");
+ return false;
+ }
+ try {
+ mService.onStartProximityUpdates(mIProximityCallback);
+ mStartedUpdates = true;
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Cannot call into the AttentionService", e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void cancelUpdates() {
+ synchronized (mLock) {
+ if (mStartedUpdates) {
+ if (mService == null) {
+ mStartedUpdates = false;
+ return;
+ }
+ try {
+ mService.onStopProximityUpdates();
+ mStartedUpdates = false;
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Cannot call into the AttentionService", e);
+ }
+ }
+ }
+ }
+
+ void dump(IndentingPrintWriter ipw) {
+ ipw.increaseIndent();
+ ipw.println("is StartedUpdates=" + mStartedUpdates);
+ ipw.decreaseIndent();
+ }
+ }
+
private void appendResultToAttentionCacheBuffer(AttentionCheckCache cache) {
synchronized (mLock) {
if (mAttentionCheckCacheBuffer == null) {
@@ -593,6 +755,18 @@
mCurrentAttentionCheck.mCallbackInternal.onFailure(ATTENTION_FAILURE_UNKNOWN);
}
}
+ if (mCurrentProximityUpdate != null && mCurrentProximityUpdate.mStartedUpdates) {
+ if (mService != null) {
+ try {
+ mService.onStartProximityUpdates(mCurrentProximityUpdate.mIProximityCallback);
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Cannot call into the AttentionService", e);
+ }
+ } else {
+ mCurrentProximityUpdate.cancelUpdates();
+ mCurrentProximityUpdate = null;
+ }
+ }
}
@VisibleForTesting
@@ -609,7 +783,9 @@
switch (msg.what) {
// Do not occupy resources when not in use - unbind proactively.
case CHECK_CONNECTION_EXPIRATION: {
- cancelAndUnbindLocked();
+ synchronized (mLock) {
+ cancelAndUnbindLocked();
+ }
}
break;
@@ -653,10 +829,15 @@
@GuardedBy("mLock")
private void cancelAndUnbindLocked() {
synchronized (mLock) {
- if (mCurrentAttentionCheck == null) {
+ if (mCurrentAttentionCheck == null && mCurrentProximityUpdate == null) {
return;
}
- cancel();
+ if (mCurrentAttentionCheck != null) {
+ cancel();
+ }
+ if (mCurrentProximityUpdate != null) {
+ mCurrentProximityUpdate.cancelUpdates();
+ }
if (mService == null) {
return;
}
@@ -702,7 +883,9 @@
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
- cancelAndUnbindLocked();
+ synchronized (mLock) {
+ cancelAndUnbindLocked();
+ }
}
}
}
@@ -730,8 +913,27 @@
}
}
+ class TestableProximityCallbackInternal extends ProximityCallbackInternal {
+ private double mLastCallbackCode = PROXIMITY_UNKNOWN;
+
+ @Override
+ public void onProximityUpdate(double distance) {
+ mLastCallbackCode = distance;
+ }
+
+ public void reset() {
+ mLastCallbackCode = PROXIMITY_UNKNOWN;
+ }
+
+ public double getLastCallbackCode() {
+ return mLastCallbackCode;
+ }
+ }
+
final TestableAttentionCallbackInternal mTestableAttentionCallback =
new TestableAttentionCallbackInternal();
+ final TestableProximityCallbackInternal mTestableProximityCallback =
+ new TestableProximityCallbackInternal();
@Override
public int onCommand(@Nullable final String cmd) {
@@ -749,6 +951,10 @@
return cmdCallCheckAttention();
case "cancelCheckAttention":
return cmdCallCancelAttention();
+ case "onStartProximityUpdates":
+ return cmdCallOnStartProximityUpdates();
+ case "onStopProximityUpdates":
+ return cmdCallOnStopProximityUpdates();
default:
throw new IllegalArgumentException("Invalid argument");
}
@@ -758,6 +964,8 @@
return cmdClearTestableAttentionService();
case "getLastTestCallbackCode":
return cmdGetLastTestCallbackCode();
+ case "getLastTestProximityCallbackCode":
+ return cmdGetLastTestProximityCallbackCode();
default:
return handleDefaultCommands(cmd);
}
@@ -782,6 +990,7 @@
private int cmdClearTestableAttentionService() {
sTestAttentionServicePackage = "";
mTestableAttentionCallback.reset();
+ mTestableProximityCallback.reset();
resetStates();
return 0;
}
@@ -800,6 +1009,20 @@
return 0;
}
+ private int cmdCallOnStartProximityUpdates() {
+ final PrintWriter out = getOutPrintWriter();
+ boolean calledSuccessfully = onStartProximityUpdates(mTestableProximityCallback);
+ out.println(calledSuccessfully ? "true" : "false");
+ return 0;
+ }
+
+ private int cmdCallOnStopProximityUpdates() {
+ final PrintWriter out = getOutPrintWriter();
+ onStopProximityUpdates(mTestableProximityCallback);
+ out.println("true");
+ return 0;
+ }
+
private int cmdResolveAttentionServiceComponent() {
final PrintWriter out = getOutPrintWriter();
ComponentName resolvedComponent = resolveAttentionService(mContext);
@@ -813,7 +1036,16 @@
return 0;
}
+ private int cmdGetLastTestProximityCallbackCode() {
+ final PrintWriter out = getOutPrintWriter();
+ out.println(mTestableProximityCallback.getLastCallbackCode());
+ return 0;
+ }
+
private void resetStates() {
+ synchronized (mLock) {
+ mCurrentProximityUpdate = null;
+ }
mComponentName = resolveAttentionService(mContext);
}
@@ -844,11 +1076,24 @@
+ " (to see the result, call getLastTestCallbackCode)");
out.println(" := false, otherwise");
out.println(" call cancelCheckAttention: Cancels check attention");
+ out.println(" call onStartProximityUpdates: Calls onStartProximityUpdates");
+ out.println(" ---returns:");
+ out.println(
+ " := true, if the request was successfully dispatched to the service "
+ + "implementation."
+ + " (to see the result, call getLastTestProximityCallbackCode)");
+ out.println(" := false, otherwise");
+ out.println(" call onStopProximityUpdates: Cancels proximity updates");
out.println(" getLastTestCallbackCode");
out.println(" ---returns:");
out.println(
" := An integer, representing the last callback code received from the "
+ "bounded implementation. If none, it will return -1");
+ out.println(" getLastTestProximityCallbackCode");
+ out.println(" ---returns:");
+ out.println(
+ " := A double, representing the last proximity value received from the "
+ + "bounded implementation. If none, it will return -1.0");
}
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 42fca9b..47f31d5 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -486,9 +486,7 @@
return;
}
final BluetoothDevice btDevice = deviceList.get(0);
- final @BluetoothProfile.BtProfileState int state =
- proxy.getConnectionState(btDevice);
- if (state == BluetoothProfile.STATE_CONNECTED) {
+ if (proxy.getConnectionState(btDevice) == BluetoothProfile.STATE_CONNECTED) {
mDeviceBroker.queueOnBluetoothActiveDeviceChanged(
new AudioDeviceBroker.BtDeviceChangedData(btDevice, null,
new BtProfileConnectionInfo(profile), "mBluetoothProfileServiceListener"));
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 0da6a1b..79705a3 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -53,6 +53,7 @@
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.common.OperationContext;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -64,6 +65,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.log.BiometricFrameworkStatsLogger;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -681,16 +683,18 @@
+ ", Latency: " + latency);
}
- FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
+ final OperationContext operationContext = new OperationContext();
+ operationContext.isCrypto = isCrypto();
+ BiometricFrameworkStatsLogger.getInstance().authenticate(
+ operationContext,
statsModality(),
- mUserId,
- isCrypto(),
+ BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
- mPreAuthInfo.confirmationRequested,
- FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
- latency,
mDebugEnabled,
- -1 /* sensorId */,
+ latency,
+ FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
+ mPreAuthInfo.confirmationRequested,
+ mUserId,
-1f /* ambientLightLux */);
} else {
final long latency = System.currentTimeMillis() - mStartTimeMs;
@@ -711,17 +715,18 @@
+ ", Latency: " + latency);
}
// Auth canceled
- FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
+ final OperationContext operationContext = new OperationContext();
+ operationContext.isCrypto = isCrypto();
+ BiometricFrameworkStatsLogger.getInstance().error(
+ operationContext,
statsModality(),
- mUserId,
- isCrypto(),
BiometricsProtoEnums.ACTION_AUTHENTICATE,
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
- error,
- 0 /* vendorCode */,
mDebugEnabled,
latency,
- -1 /* sensorId */);
+ error,
+ 0 /* vendorCode */,
+ mUserId);
}
}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
new file mode 100644
index 0000000..c5e266f
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -0,0 +1,59 @@
+/*
+ * 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.biometrics.log;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.common.OperationContext;
+
+import java.util.function.Consumer;
+
+/**
+ * Cache for system state not directly related to biometric operations that is used for
+ * logging or optimizations.
+ */
+public interface BiometricContext {
+ /** Gets the context source from the system context. */
+ static BiometricContext getInstance(@NonNull Context context) {
+ return BiometricContextProvider.defaultProvider(context);
+ }
+
+ /** Update the given context with the most recent values and return it. */
+ OperationContext updateContext(@NonNull OperationContext operationContext,
+ boolean isCryptoOperation);
+
+ /** The session id for keyguard entry, if active, or null. */
+ @Nullable Integer getKeyguardEntrySessionId();
+
+ /** The session id for biometric prompt usage, if active, or null. */
+ @Nullable Integer getBiometricPromptSessionId();
+
+ /** If the display is in AOD. */
+ boolean isAoD();
+
+ /**
+ * Subscribe to context changes.
+ *
+ * @param context context that will be modified when changed
+ * @param consumer callback when the context is modified
+ */
+ void subscribe(@NonNull OperationContext context, @NonNull Consumer<OperationContext> consumer);
+
+ /** Unsubscribe from context changes. */
+ void unsubscribe(@NonNull OperationContext context);
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
new file mode 100644
index 0000000..70acaff
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -0,0 +1,184 @@
+/*
+ * 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.biometrics.log;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.hardware.biometrics.IBiometricContextListener;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.statusbar.ISessionListener;
+import com.android.internal.statusbar.IStatusBarService;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * A default provider for {@link BiometricContext}.
+ */
+class BiometricContextProvider implements BiometricContext {
+
+ private static final String TAG = "BiometricContextProvider";
+
+ private static final int SESSION_TYPES =
+ StatusBarManager.SESSION_KEYGUARD | StatusBarManager.SESSION_BIOMETRIC_PROMPT;
+
+ private static BiometricContextProvider sInstance;
+
+ static BiometricContextProvider defaultProvider(@NonNull Context context) {
+ synchronized (BiometricContextProvider.class) {
+ if (sInstance == null) {
+ try {
+ sInstance = new BiometricContextProvider(
+ new AmbientDisplayConfiguration(context),
+ IStatusBarService.Stub.asInterface(ServiceManager.getServiceOrThrow(
+ Context.STATUS_BAR_SERVICE)), null /* handler */);
+ } catch (ServiceNotFoundException e) {
+ throw new IllegalStateException("Failed to find required service", e);
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ @NonNull
+ private final Map<OperationContext, Consumer<OperationContext>> mSubscribers =
+ new ConcurrentHashMap<>();
+
+ @Nullable
+ private final Map<Integer, InstanceId> mSession = new ConcurrentHashMap<>();
+
+ private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+ private boolean mIsDozing = false;
+
+ @VisibleForTesting
+ BiometricContextProvider(@NonNull AmbientDisplayConfiguration ambientDisplayConfiguration,
+ @NonNull IStatusBarService service, @Nullable Handler handler) {
+ mAmbientDisplayConfiguration = ambientDisplayConfiguration;
+ try {
+ service.setBiometicContextListener(new IBiometricContextListener.Stub() {
+ @Override
+ public void onDozeChanged(boolean isDozing) {
+ mIsDozing = isDozing;
+ notifyChanged();
+ }
+
+ private void notifyChanged() {
+ if (handler != null) {
+ handler.post(() -> notifySubscribers());
+ } else {
+ notifySubscribers();
+ }
+ }
+ });
+ service.registerSessionListener(SESSION_TYPES, new ISessionListener.Stub() {
+ @Override
+ public void onSessionStarted(int sessionType, InstanceId instance) {
+ mSession.put(sessionType, instance);
+ }
+
+ @Override
+ public void onSessionEnded(int sessionType, InstanceId instance) {
+ final InstanceId id = mSession.remove(sessionType);
+ if (id != null && instance != null && id.getId() != instance.getId()) {
+ Slog.w(TAG, "session id mismatch");
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to register biometric context listener", e);
+ }
+ }
+
+ @Override
+ public OperationContext updateContext(@NonNull OperationContext operationContext,
+ boolean isCryptoOperation) {
+ operationContext.isAoD = isAoD();
+ operationContext.isCrypto = isCryptoOperation;
+ setFirstSessionId(operationContext);
+ return operationContext;
+ }
+
+ private void setFirstSessionId(@NonNull OperationContext operationContext) {
+ Integer sessionId = getKeyguardEntrySessionId();
+ if (sessionId != null) {
+ operationContext.id = sessionId;
+ operationContext.reason = OperationReason.KEYGUARD;
+ return;
+ }
+
+ sessionId = getBiometricPromptSessionId();
+ if (sessionId != null) {
+ operationContext.id = sessionId;
+ operationContext.reason = OperationReason.BIOMETRIC_PROMPT;
+ return;
+ }
+
+ operationContext.id = 0;
+ operationContext.reason = OperationReason.UNKNOWN;
+ }
+
+ @Nullable
+ @Override
+ public Integer getKeyguardEntrySessionId() {
+ final InstanceId id = mSession.get(StatusBarManager.SESSION_KEYGUARD);
+ return id != null ? id.getId() : null;
+ }
+
+ @Nullable
+ @Override
+ public Integer getBiometricPromptSessionId() {
+ final InstanceId id = mSession.get(StatusBarManager.SESSION_BIOMETRIC_PROMPT);
+ return id != null ? id.getId() : null;
+ }
+
+ @Override
+ public boolean isAoD() {
+ return mIsDozing && mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ public void subscribe(@NonNull OperationContext context,
+ @NonNull Consumer<OperationContext> consumer) {
+ mSubscribers.put(context, consumer);
+ }
+
+ @Override
+ public void unsubscribe(@NonNull OperationContext context) {
+ mSubscribers.remove(context);
+ }
+
+ private void notifySubscribers() {
+ mSubscribers.forEach((context, consumer) -> {
+ context.isAoD = isAoD();
+ consumer.accept(context);
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index c3471bd..8965227 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -17,6 +17,8 @@
package com.android.server.biometrics.log;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
import android.util.Slog;
import com.android.internal.util.FrameworkStatsLog;
@@ -33,42 +35,49 @@
private BiometricFrameworkStatsLogger() {}
+ /** Shared instance. */
public static BiometricFrameworkStatsLogger getInstance() {
return sInstance;
}
/** {@see FrameworkStatsLog.BIOMETRIC_ACQUIRED}. */
- public void acquired(
+ public void acquired(OperationContext operationContext,
int statsModality, int statsAction, int statsClient, boolean isDebug,
- int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) {
+ int acquiredInfo, int vendorCode, int targetUserId) {
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED,
statsModality,
targetUserId,
- isCrypto,
+ operationContext.isCrypto,
statsAction,
statsClient,
acquiredInfo,
vendorCode,
isDebug,
- -1 /* sensorId */);
+ -1 /* sensorId */,
+ operationContext.id,
+ sessionType(operationContext.reason),
+ operationContext.isAoD);
}
/** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
- public void authenticate(
+ public void authenticate(OperationContext operationContext,
int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
- boolean authenticated, int authState, boolean requireConfirmation, boolean isCrypto,
- int targetUserId, boolean isBiometricPrompt, float ambientLightLux) {
+ int authState, boolean requireConfirmation,
+ int targetUserId, float ambientLightLux) {
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
statsModality,
targetUserId,
- isCrypto,
+ operationContext.isCrypto,
statsClient,
requireConfirmation,
authState,
sanitizeLatency(latency),
isDebug,
-1 /* sensorId */,
- ambientLightLux);
+ ambientLightLux,
+ operationContext.id,
+ sessionType(operationContext.reason),
+ operationContext.isAoD);
}
/** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */
@@ -84,20 +93,23 @@
}
/** {@see FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED}. */
- public void error(
+ public void error(OperationContext operationContext,
int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
- int error, int vendorCode, boolean isCrypto, int targetUserId) {
+ int error, int vendorCode, int targetUserId) {
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
statsModality,
targetUserId,
- isCrypto,
+ operationContext.isCrypto,
statsAction,
statsClient,
error,
vendorCode,
isDebug,
sanitizeLatency(latency),
- -1 /* sensorId */);
+ -1 /* sensorId */,
+ operationContext.id,
+ sessionType(operationContext.reason),
+ operationContext.isAoD);
}
/** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */
@@ -123,4 +135,14 @@
}
return latency;
}
+
+ private static int sessionType(@OperationReason byte reason) {
+ if (reason == OperationReason.BIOMETRIC_PROMPT) {
+ return BiometricsProtoEnums.SESSION_TYPE_BIOMETRIC_PROMPT;
+ }
+ if (reason == OperationReason.KEYGUARD) {
+ return BiometricsProtoEnums.SESSION_TYPE_KEYGUARD_ENTRY;
+ }
+ return BiometricsProtoEnums.SESSION_TYPE_UNKNOWN;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index d029af3..2a8d9f1 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -25,6 +25,7 @@
import android.hardware.SensorManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.util.Slog;
@@ -79,6 +80,12 @@
}
};
+ /** Get a new logger with all unknown fields (for operations that do not require logs). */
+ public static BiometricLogger ofUnknown(@NonNull Context context) {
+ return new BiometricLogger(context, BiometricsProtoEnums.MODALITY_UNKNOWN,
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ }
+
/**
* @param context system_server context
* @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants.
@@ -103,6 +110,11 @@
mSensorManager = sensorManager;
}
+ /** Creates a new logger with the action replaced with the new action. */
+ public BiometricLogger swapAction(@NonNull Context context, int statsAction) {
+ return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient);
+ }
+
/** Disable logging metrics and only log critical events, such as system health issues. */
public void disableMetrics() {
mShouldLogMetrics = false;
@@ -133,8 +145,8 @@
}
/** Log an acquisition event. */
- public void logOnAcquired(Context context,
- int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) {
+ public void logOnAcquired(Context context, OperationContext operationContext,
+ int acquiredInfo, int vendorCode, int targetUserId) {
if (!mShouldLogMetrics) {
return;
}
@@ -154,7 +166,7 @@
if (DEBUG) {
Slog.v(TAG, "Acquired! Modality: " + mStatsModality
+ ", User: " + targetUserId
- + ", IsCrypto: " + isCrypto
+ + ", IsCrypto: " + operationContext.isCrypto
+ ", Action: " + mStatsAction
+ ", Client: " + mStatsClient
+ ", AcquiredInfo: " + acquiredInfo
@@ -165,14 +177,14 @@
return;
}
- mSink.acquired(mStatsModality, mStatsAction, mStatsClient,
+ mSink.acquired(operationContext, mStatsModality, mStatsAction, mStatsClient,
Utils.isDebugEnabled(context, targetUserId),
- acquiredInfo, vendorCode, isCrypto, targetUserId);
+ acquiredInfo, vendorCode, targetUserId);
}
/** Log an error during an operation. */
- public void logOnError(Context context,
- int error, int vendorCode, boolean isCrypto, int targetUserId) {
+ public void logOnError(Context context, OperationContext operationContext,
+ int error, int vendorCode, int targetUserId) {
if (!mShouldLogMetrics) {
return;
}
@@ -183,7 +195,7 @@
if (DEBUG) {
Slog.v(TAG, "Error! Modality: " + mStatsModality
+ ", User: " + targetUserId
- + ", IsCrypto: " + isCrypto
+ + ", IsCrypto: " + operationContext.isCrypto
+ ", Action: " + mStatsAction
+ ", Client: " + mStatsClient
+ ", Error: " + error
@@ -197,14 +209,14 @@
return;
}
- mSink.error(mStatsModality, mStatsAction, mStatsClient,
+ mSink.error(operationContext, mStatsModality, mStatsAction, mStatsClient,
Utils.isDebugEnabled(context, targetUserId), latency,
- error, vendorCode, isCrypto, targetUserId);
+ error, vendorCode, targetUserId);
}
/** Log authentication attempt. */
- public void logOnAuthenticated(Context context,
- boolean authenticated, boolean requireConfirmation, boolean isCrypto,
+ public void logOnAuthenticated(Context context, OperationContext operationContext,
+ boolean authenticated, boolean requireConfirmation,
int targetUserId, boolean isBiometricPrompt) {
if (!mShouldLogMetrics) {
return;
@@ -230,7 +242,7 @@
if (DEBUG) {
Slog.v(TAG, "Authenticated! Modality: " + mStatsModality
+ ", User: " + targetUserId
- + ", IsCrypto: " + isCrypto
+ + ", IsCrypto: " + operationContext.isCrypto
+ ", Client: " + mStatsClient
+ ", RequireConfirmation: " + requireConfirmation
+ ", State: " + authState
@@ -244,10 +256,9 @@
return;
}
- mSink.authenticate(mStatsModality, mStatsAction, mStatsClient,
+ mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient,
Utils.isDebugEnabled(context, targetUserId),
- latency, authenticated, authState, requireConfirmation, isCrypto,
- targetUserId, isBiometricPrompt, mLastAmbientLux);
+ latency, authState, requireConfirmation, targetUserId, mLastAmbientLux);
}
/** Log enrollment outcome. */
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index 8b8103e..0f0032b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -29,6 +29,9 @@
import android.os.Vibrator;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
import java.util.function.Supplier;
/**
@@ -57,9 +60,9 @@
public AcquisitionClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int cookie, int sensorId, boolean shouldVibrate,
- int statsModality, int statsAction, int statsClient) {
- super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId, statsModality,
- statsAction, statsClient);
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId,
+ logger, biometricContext);
mPowerManager = context.getSystemService(PowerManager.class);
mShouldVibrate = shouldVibrate;
}
@@ -107,8 +110,8 @@
// that do not handle lockout under the HAL. In these cases, ensure that the framework only
// sends errors once per ClientMonitor.
if (mShouldSendErrorToClient) {
- getLogger().logOnError(getContext(), errorCode, vendorCode,
- isCryptoOperation(), getTargetUserId());
+ getLogger().logOnError(getContext(), getOperationContext(),
+ errorCode, vendorCode, getTargetUserId());
try {
if (getListener() != null) {
mShouldSendErrorToClient = false;
@@ -166,8 +169,8 @@
protected final void onAcquiredInternal(int acquiredInfo, int vendorCode,
boolean shouldSend) {
- getLogger().logOnAcquired(getContext(), acquiredInfo, vendorCode,
- isCryptoOperation(), getTargetUserId());
+ getLogger().logOnAcquired(getContext(), getOperationContext(),
+ acquiredInfo, vendorCode, getTargetUserId());
if (DEBUG) {
Slog.v(TAG, "Acquired: " + acquiredInfo + " " + vendorCode
+ ", shouldSend: " + shouldSend);
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index b715faf..54b79e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -29,7 +29,6 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -39,6 +38,8 @@
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.ArrayList;
import java.util.List;
@@ -93,13 +94,13 @@
public AuthenticationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int targetUserId, long operationId, boolean restricted, @NonNull String owner,
- int cookie, boolean requireConfirmation, int sensorId, boolean isStrongBiometric,
- int statsModality, int statsClient, @Nullable TaskStackListener taskStackListener,
+ int cookie, boolean requireConfirmation, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener,
@NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
boolean shouldVibrate, boolean isKeyguardBypassEnabled) {
super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
- shouldVibrate, statsModality, BiometricsProtoEnums.ACTION_AUTHENTICATE,
- statsClient);
+ shouldVibrate, biometricLogger, biometricContext);
mIsStrongBiometric = isStrongBiometric;
mOperationId = operationId;
mRequireConfirmation = requireConfirmation;
@@ -165,8 +166,8 @@
@Override
public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
boolean authenticated, ArrayList<Byte> hardwareAuthToken) {
- getLogger().logOnAuthenticated(getContext(), authenticated, mRequireConfirmation,
- isCryptoOperation(), getTargetUserId(), isBiometricPrompt());
+ getLogger().logOnAuthenticated(getContext(), getOperationContext(),
+ authenticated, mRequireConfirmation, getTargetUserId(), isBiometricPrompt());
final ClientMonitorCallbackConverter listener = getListener();
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index e1f7e2a..1b2e606 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -21,12 +21,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import java.util.NoSuchElementException;
@@ -50,6 +50,7 @@
@NonNull private final String mOwner;
private final int mSensorId; // sensorId as configured by the framework
@NonNull private final BiometricLogger mLogger;
+ @NonNull private final BiometricContext mBiometricContext;
@Nullable private IBinder mToken;
private long mRequestId;
@@ -82,22 +83,13 @@
* @param owner name of the client that owns this
* @param cookie BiometricPrompt authentication cookie (to be moved into a subclass soon)
* @param sensorId ID of the sensor that the operation should be requested of
- * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants
- * @param statsAction One of {@link BiometricsProtoEnums} ACTION_* constants
- * @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants
+ * @param logger framework stats logger
+ * @param biometricContext system context metadata
*/
public BaseClientMonitor(@NonNull Context context,
@Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction,
- int statsClient) {
- this(context, token, listener, userId, owner, cookie, sensorId,
- new BiometricLogger(context, statsModality, statsAction, statsClient));
- }
-
- @VisibleForTesting
- BaseClientMonitor(@NonNull Context context,
- @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int cookie, int sensorId, @NonNull BiometricLogger logger) {
+ @NonNull String owner, int cookie, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
mSequentialId = sCount++;
mContext = context;
mToken = token;
@@ -108,6 +100,7 @@
mCookie = cookie;
mSensorId = sensorId;
mLogger = logger;
+ mBiometricContext = biometricContext;
try {
if (token != null) {
@@ -207,20 +200,29 @@
return false;
}
+ /** System context that may change during operations. */
+ @NonNull
+ protected BiometricContext getBiometricContext() {
+ return mBiometricContext;
+ }
+
/** Logger for this client */
@NonNull
public BiometricLogger getLogger() {
return mLogger;
}
+ @NonNull
public final Context getContext() {
return mContext;
}
+ @NonNull
public final String getOwnerString() {
return mOwner;
}
+ @Nullable
public final ClientMonitorCallbackConverter getListener() {
return mListener;
}
@@ -229,6 +231,7 @@
return mTargetUserId;
}
+ @Nullable
public final IBinder getToken() {
return mToken;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 74f4931..483ce75 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -20,13 +20,14 @@
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.fingerprint.FingerprintManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.Arrays;
import java.util.function.Supplier;
@@ -53,10 +54,10 @@
public EnrollClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils,
- int timeoutSec, int statsModality, int sensorId, boolean shouldVibrate) {
+ int timeoutSec, int sensorId, boolean shouldVibrate,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- shouldVibrate, statsModality, BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ shouldVibrate, logger, biometricContext);
mBiometricUtils = utils;
mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length);
mTimeoutSec = timeoutSec;
diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
index 9689418..2adf0cb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -18,12 +18,13 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.function.Supplier;
@@ -33,10 +34,10 @@
public GenerateChallengeClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
- int userId, @NonNull String owner, int sensorId) {
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ biometricLogger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
index 66a1c6e..a6e8911 100644
--- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
@@ -19,9 +19,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
import android.os.IBinder;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
import java.util.function.Supplier;
/**
@@ -33,6 +36,9 @@
@NonNull
protected final Supplier<T> mLazyDaemon;
+ @NonNull
+ private final OperationContext mOperationContext = new OperationContext();
+
/**
* @param context system_server context
* @param lazyDaemon pointer for lazy retrieval of the HAL
@@ -42,16 +48,15 @@
* @param owner name of the client that owns this
* @param cookie BiometricPrompt authentication cookie (to be moved into a subclass soon)
* @param sensorId ID of the sensor that the operation should be requested of
- * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants
- * @param statsAction One of {@link BiometricsProtoEnums} ACTION_* constants
- * @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants
+ * @param biometricLogger framework stats logger
+ * @param biometricContext system context metadata
*/
public HalClientMonitor(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction,
- int statsClient) {
- super(context, token, listener, userId, owner, cookie, sensorId, statsModality,
- statsAction, statsClient);
+ @NonNull String owner, int cookie, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
+ super(context, token, listener, userId, owner, cookie, sensorId,
+ biometricLogger, biometricContext);
mLazyDaemon = lazyDaemon;
}
@@ -71,4 +76,29 @@
* {@link #start(ClientMonitorCallback)}.
*/
public abstract void unableToStart();
+
+ @Override
+ public void destroy() {
+ super.destroy();
+
+ // subclasses should do this earlier in most cases, but ensure it happens now
+ unsubscribeBiometricContext();
+ }
+
+ protected OperationContext getOperationContext() {
+ return getBiometricContext().updateContext(mOperationContext, isCryptoOperation());
+ }
+
+ protected ClientMonitorCallback getBiometricContextUnsubscriber() {
+ return new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor monitor, boolean success) {
+ unsubscribeBiometricContext();
+ }
+ };
+ }
+
+ protected void unsubscribeBiometricContext() {
+ getBiometricContext().unsubscribe(mOperationContext);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 0e6d11e..57ea812 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -19,11 +19,12 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.ArrayList;
import java.util.List;
@@ -101,19 +102,22 @@
protected abstract InternalEnumerateClient<T> getEnumerateClient(Context context,
Supplier<T> lazyDaemon, IBinder token, int userId, String owner,
- List<S> enrolledList, BiometricUtils<S> utils, int sensorId);
+ List<S> enrolledList, BiometricUtils<S> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext);
protected abstract RemovalClient<S, T> getRemovalClient(Context context,
Supplier<T> lazyDaemon, IBinder token, int biometricId, int userId, String owner,
- BiometricUtils<S> utils, int sensorId, Map<Integer, Long> authenticatorIds);
+ BiometricUtils<S> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ Map<Integer, Long> authenticatorIds);
protected InternalCleanupClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
- int userId, @NonNull String owner, int sensorId, int statsModality,
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull List<S> enrolledList, @NonNull BiometricUtils<S> utils,
@NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, null /* token */, null /* ClientMonitorCallbackConverter */,
- userId, owner, 0 /* cookie */, sensorId, statsModality,
- BiometricsProtoEnums.ACTION_ENUMERATE, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ userId, owner, 0 /* cookie */, sensorId, logger, biometricContext);
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
mEnrolledList = enrolledList;
@@ -127,7 +131,8 @@
mUnknownHALTemplates.remove(template);
mCurrentTask = getRemovalClient(getContext(), mLazyDaemon, getToken(),
template.mIdentifier.getBiometricId(), template.mUserId,
- getContext().getPackageName(), mBiometricUtils, getSensorId(), mAuthenticatorIds);
+ getContext().getPackageName(), mBiometricUtils, getSensorId(),
+ getLogger(), getBiometricContext(), mAuthenticatorIds);
getLogger().logUnknownEnrollmentInHal();
@@ -145,7 +150,8 @@
// Start enumeration. Removal will start if necessary, when enumeration is completed.
mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(),
- getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId());
+ getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId(), getLogger(),
+ getBiometricContext());
Slog.d(TAG, "Starting enumerate: " + mCurrentTask);
mCurrentTask.start(mEnumerateCallback);
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 5f97f37..7f8f38f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -19,11 +19,12 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.ArrayList;
import java.util.List;
@@ -47,12 +48,14 @@
protected InternalEnumerateClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, int userId, @NonNull String owner,
@NonNull List<? extends BiometricAuthenticator.Identifier> enrolledList,
- @NonNull BiometricUtils utils, int sensorId, int statsModality) {
+ @NonNull BiometricUtils utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
// Internal enumerate does not need to send results to anyone. Cleanup (enumerate + remove)
// is all done internally.
super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
- 0 /* cookie */, sensorId, statsModality, BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
+ //, BiometricsProtoEnums.ACTION_ENUMERATE,
+ // BiometricsProtoEnums.CLIENT_UNKNOWN);
mEnrolledList = enrolledList;
mUtils = utils;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
index 697d77c..d5aa5e2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
@@ -19,12 +19,13 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IInvalidationCallback;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.Map;
import java.util.function.Supplier;
@@ -42,12 +43,13 @@
@NonNull private final IInvalidationCallback mInvalidationCallback;
public InvalidationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
- int userId, int sensorId, @NonNull Map<Integer, Long> authenticatorIds,
+ int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull Map<Integer, Long> authenticatorIds,
@NonNull IInvalidationCallback callback) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId,
context.getOpPackageName(), 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ logger, biometricContext);
mAuthenticatorIds = authenticatorIds;
mInvalidationCallback = callback;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
index b2661a2..1097bb7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
@@ -20,10 +20,11 @@
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IInvalidationCallback;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
/**
* ClientMonitor subclass responsible for coordination of authenticatorId invalidation of other
@@ -74,11 +75,10 @@
};
public InvalidationRequesterClient(@NonNull Context context, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull BiometricUtils<S> utils) {
super(context, null /* token */, null /* listener */, userId,
- context.getOpPackageName(), 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ context.getOpPackageName(), 0 /* cookie */, sensorId, logger, biometricContext);
mBiometricManager = context.getSystemService(BiometricManager.class);
mUtils = utils;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index a0cef94..07ce841 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -19,12 +19,13 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.Map;
import java.util.function.Supplier;
@@ -44,10 +45,12 @@
public RemovalClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int userId, @NonNull String owner, @NonNull BiometricUtils<S> utils, int sensorId,
- @NonNull Map<Integer, Long> authenticatorIds, int statsModality) {
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- statsModality, BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ logger, biometricContext);
+ //, BiometricsProtoEnums.ACTION_REMOVE,
+ // BiometricsProtoEnums.CLIENT_UNKNOWN);
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
index 7d83863..88f4da2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
@@ -18,20 +18,21 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.function.Supplier;
public abstract class RevokeChallengeClient<T> extends HalClientMonitor<T> {
public RevokeChallengeClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
- @NonNull IBinder token, int userId, @NonNull String owner, int sensorId) {
+ @NonNull IBinder token, int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, biometricLogger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
index 1bc3248..21c9f64 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -19,11 +19,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.function.Supplier;
@@ -47,10 +48,10 @@
public StartUserClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull UserStartedCallback<U> callback) {
super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mUserStartedCallback = callback;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
index 3eafbb8..e8654dc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -19,11 +19,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.function.Supplier;
@@ -47,10 +48,10 @@
public StopUserClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull UserStoppedCallback callback) {
super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mUserStoppedCallback = callback;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 039b08e..2e82057 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -57,6 +57,7 @@
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -645,7 +646,7 @@
try {
final SensorProps[] props = face.getSensorProps();
final FaceProvider provider = new FaceProvider(getContext(), props, instance,
- mLockoutResetDispatcher);
+ mLockoutResetDispatcher, BiometricContext.getInstance(getContext()));
mServiceProviders.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
index 006667a..29eee6b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
@@ -16,11 +16,11 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback;
+
import android.annotation.NonNull;
import android.hardware.biometrics.face.ISession;
-import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback;
-
/**
* A holder for an AIDL {@link ISession} with additional metadata about the current user
* and the backend.
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index c4e0502..7765ab3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -25,10 +25,7 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
import android.hardware.biometrics.face.IFace;
import android.hardware.face.FaceAuthenticationFrame;
import android.hardware.face.FaceManager;
@@ -37,7 +34,10 @@
import android.util.Slog;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -76,19 +76,36 @@
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
- boolean isStrongBiometric, int statsClient, @NonNull UsageStats usageStats,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric, @NonNull UsageStats usageStats,
@NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
boolean isKeyguardBypassEnabled) {
+ this(context, lazyDaemon, token, requestId, listener, targetUserId, operationId,
+ restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+ isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
+ isKeyguardBypassEnabled, context.getSystemService(SensorPrivacyManager.class));
+ }
+
+ @VisibleForTesting
+ FaceAuthenticationClient(@NonNull Context context,
+ @NonNull Supplier<AidlSession> lazyDaemon,
+ @NonNull IBinder token, long requestId,
+ @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
+ boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric, @NonNull UsageStats usageStats,
+ @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
+ boolean isKeyguardBypassEnabled, SensorPrivacyManager sensorPrivacyManager) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
- owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
- BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
- lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
+ owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+ isStrongBiometric, null /* taskStackListener */, lockoutCache,
+ allowBackgroundAuthentication, true /* shouldVibrate */,
isKeyguardBypassEnabled);
setRequestId(requestId);
mUsageStats = usageStats;
mLockoutCache = lockoutCache;
mNotificationManager = context.getSystemService(NotificationManager.class);
- mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+ mSensorPrivacyManager = sensorPrivacyManager;
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -138,13 +155,8 @@
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
- final OperationContext context = new OperationContext();
- // TODO: add reason, id, and isAoD
- context.id = 0;
- context.reason = OperationReason.UNKNOWN;
- context.isAoD = false;
- context.isCrypto = isCryptoOperation();
- return session.getSession().authenticateWithContext(mOperationId, context);
+ return session.getSession().authenticateWithContext(
+ mOperationId, getOperationContext());
} else {
return session.getSession().authenticate(mOperationId);
}
@@ -263,8 +275,8 @@
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
// Lockout metrics are logged as an error code.
final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT;
- getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
- isCryptoOperation(), getTargetUserId());
+ getLogger().logOnError(getContext(), getOperationContext(),
+ error, 0 /* vendorCode */, getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -279,8 +291,8 @@
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
// Lockout metrics are logged as an error code.
final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
- getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
- isCryptoOperation(), getTargetUserId());
+ getLogger().logOnError(getContext(), getOperationContext(),
+ error, 0 /* vendorCode */, getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 3f3db43..efedcf8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -21,15 +21,15 @@
import android.content.Context;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -52,13 +52,26 @@
FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId, boolean isStrongBiometric, int statsClient) {
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric) {
+ this(context, lazyDaemon, token, requestId, listener, userId, owner, sensorId,
+ logger, biometricContext, isStrongBiometric,
+ context.getSystemService(SensorPrivacyManager.class));
+ }
+
+ @VisibleForTesting
+ FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+ @NonNull IBinder token, long requestId,
+ @NonNull ClientMonitorCallbackConverter listener, int userId,
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric, SensorPrivacyManager sensorPrivacyManager) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FACE,
- BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ true /* shouldVibrate */, logger, biometricContext);
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
- mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+ mSensorPrivacyManager = sensorPrivacyManager;
}
@Override
@@ -101,13 +114,7 @@
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
- final OperationContext context = new OperationContext();
- // TODO: add reason, id, and isAoD
- context.id = 0;
- context.reason = OperationReason.UNKNOWN;
- context.isAoD = false;
- context.isCrypto = isCryptoOperation();
- return session.getSession().detectInteractionWithContext(context);
+ return session.getSession().detectInteractionWithContext(getOperationContext());
} else {
return session.getSession().detectInteraction();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 8dc53b6..da78536 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -20,10 +20,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
import android.hardware.biometrics.face.EnrollmentType;
import android.hardware.biometrics.face.Feature;
import android.hardware.biometrics.face.IFace;
@@ -40,6 +37,8 @@
import com.android.internal.R;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricUtils;
@@ -89,11 +88,11 @@
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
- @Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser,
- boolean debugConsent) {
+ @Nullable Surface previewSurface, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ int maxTemplatesPerUser, boolean debugConsent) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
- timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
- false /* shouldVibrate */);
+ timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext);
setRequestId(requestId);
mEnrollIgnoreList = getContext().getResources()
.getIntArray(R.array.config_face_acquire_enroll_ignorelist);
@@ -199,14 +198,8 @@
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
if (session.hasContextMethods()) {
- final OperationContext context = new OperationContext();
- // TODO: add reason, id, and isAoD
- context.id = 0;
- context.reason = OperationReason.UNKNOWN;
- context.isAoD = false;
- context.isCrypto = isCryptoOperation();
return session.getSession().enrollWithContext(
- hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, context);
+ hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, getOperationContext());
} else {
return session.getSession().enroll(hat, EnrollmentType.DEFAULT, features,
mHwPreviewHandle);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index bdad268..165c3a2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -23,6 +23,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.GenerateChallengeClient;
@@ -37,8 +39,10 @@
FaceGenerateChallengeClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId) {
- super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
+ biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
index 2f3187b..1f4f612 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
@@ -18,11 +18,12 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -38,10 +39,10 @@
FaceGetAuthenticatorIdClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
int userId, @NonNull String opPackageName, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, opPackageName,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FACE,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mAuthenticatorIds = authenticatorIds;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index 79479be..ef3b345 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -20,7 +20,6 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
import android.os.IBinder;
import android.os.RemoteException;
@@ -28,6 +27,8 @@
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ErrorConsumer;
@@ -48,10 +49,10 @@
FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId) {
+ @NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ logger, biometricContext);
mUserId = userId;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index a2b0339..54f2033 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -18,11 +18,12 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
import android.hardware.face.Face;
import android.os.IBinder;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalCleanupClient;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -39,29 +40,32 @@
FaceInternalCleanupClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
- int sensorId, @NonNull List<Face> enrolledList, @NonNull BiometricUtils<Face> utils,
- @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, userId, owner, sensorId, BiometricsProtoEnums.MODALITY_FACE,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList,
+ @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
enrolledList, utils, authenticatorIds);
}
@Override
protected InternalEnumerateClient<AidlSession> getEnumerateClient(Context context,
Supplier<AidlSession> lazyDaemon, IBinder token, int userId, String owner,
- List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId) {
+ List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
- enrolledList, utils, sensorId);
+ enrolledList, utils, sensorId, logger, biometricContext);
}
@Override
protected RemovalClient<Face, AidlSession> getRemovalClient(Context context,
Supplier<AidlSession> lazyDaemon, IBinder token,
int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
Map<Integer, Long> authenticatorIds) {
// Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
// is all done internally.
return new FaceRemovalClient(context, lazyDaemon, token,
null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
- utils, sensorId, authenticatorIds);
+ utils, sensorId, logger, biometricContext, authenticatorIds);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
index 88c9d3b..d85455e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
@@ -18,13 +18,14 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
import android.hardware.face.Face;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -40,9 +41,10 @@
FaceInternalEnumerateClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId,
@NonNull String owner, @NonNull List<Face> enrolledList,
- @NonNull BiometricUtils<Face> utils, int sensorId) {
+ @NonNull BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
- BiometricsProtoEnums.MODALITY_FACE);
+ logger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
index 04ea2cfc..39d8de0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
@@ -23,6 +23,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.InvalidationClient;
import java.util.Map;
@@ -33,8 +35,10 @@
public FaceInvalidationClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull Map<Integer, Long> authenticatorIds, @NonNull IInvalidationCallback callback) {
- super(context, lazyDaemon, userId, sensorId, authenticatorIds, callback);
+ super(context, lazyDaemon, userId, sensorId, logger, biometricContext,
+ authenticatorIds, callback);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 9d7a552..4e03ee9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -23,6 +23,7 @@
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
@@ -47,6 +48,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -87,7 +90,7 @@
@NonNull private final BiometricTaskStackListener mTaskStackListener;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
-
+ @NonNull private final BiometricContext mBiometricContext;
@Nullable private IFace mDaemon;
private final class BiometricTaskStackListener extends TaskStackListener {
@@ -125,7 +128,8 @@
public FaceProvider(@NonNull Context context, @NonNull SensorProps[] props,
@NonNull String halInstanceName,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext) {
mContext = context;
mHalInstanceName = halInstanceName;
mSensors = new SparseArray<>();
@@ -134,6 +138,7 @@
mLockoutResetDispatcher = lockoutResetDispatcher;
mActivityTaskManager = ActivityTaskManager.getInstance();
mTaskStackListener = new BiometricTaskStackListener();
+ mBiometricContext = biometricContext;
for (SensorProps prop : props) {
final int sensorId = prop.commonProps.sensorId;
@@ -153,7 +158,7 @@
prop.supportsDetectInteraction, prop.halControlsPreview,
false /* resetLockoutRequiresChallenge */);
final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
- internalProp, lockoutResetDispatcher);
+ internalProp, lockoutResetDispatcher, mBiometricContext);
mSensors.put(sensorId, sensor);
Slog.d(getTag(), "Added: " + internalProp);
@@ -237,6 +242,9 @@
final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
mContext, mSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client);
@@ -247,6 +255,8 @@
mHandler.post(() -> {
final InvalidationRequesterClient<Face> client =
new InvalidationRequesterClient<>(mContext, userId, sensorId,
+ BiometricLogger.ofUnknown(mContext),
+ mBiometricContext,
FaceUtils.getInstance(sensorId));
scheduleForSensor(sensorId, client);
});
@@ -285,6 +295,9 @@
mHandler.post(() -> {
final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
mSensors.get(sensorId).getLazySession(), userId, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds(), callback);
scheduleForSensor(sensorId, client);
});
@@ -311,7 +324,10 @@
mHandler.post(() -> {
final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId);
+ new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext);
scheduleForSensor(sensorId, client);
});
}
@@ -322,7 +338,9 @@
mHandler.post(() -> {
final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
mSensors.get(sensorId).getLazySession(), token, userId, opPackageName, sensorId,
- challenge);
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, challenge);
scheduleForSensor(sensorId, client);
});
}
@@ -340,8 +358,10 @@
mSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
- ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
- debugConsent);
+ ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, maxTemplatesPerUser, debugConsent);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -372,8 +392,9 @@
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FaceDetectClient client = new FaceDetectClient(mContext,
mSensors.get(sensorId).getLazySession(),
- token, id, callback, userId, opPackageName,
- sensorId, isStrongBiometric, statsClient);
+ token, id, callback, userId, opPackageName, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext, isStrongBiometric);
scheduleForSensor(sensorId, client);
});
@@ -396,7 +417,9 @@
final FaceAuthenticationClient client = new FaceAuthenticationClient(
mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
userId, operationId, restricted, opPackageName, cookie,
- false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+ false /* requireConfirmation */, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext, isStrongBiometric,
mUsageStats, mSensors.get(sensorId).getLockoutCache(),
allowBackgroundAuthentication, isKeyguardBypassEnabled);
scheduleForSensor(sensorId, client);
@@ -450,6 +473,9 @@
mSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), faceIds, userId,
opPackageName, FaceUtils.getInstance(sensorId), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client);
});
@@ -460,7 +486,10 @@
mHandler.post(() -> {
final FaceResetLockoutClient client = new FaceResetLockoutClient(
mContext, mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+ mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, hardwareAuthToken,
mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
scheduleForSensor(sensorId, client);
@@ -481,7 +510,9 @@
final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
mSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId,
- mContext.getOpPackageName(), sensorId, feature, enabled, hardwareAuthToken);
+ mContext.getOpPackageName(), sensorId,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
+ feature, enabled, hardwareAuthToken);
scheduleForSensor(sensorId, client);
});
}
@@ -498,7 +529,8 @@
}
final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
mSensors.get(sensorId).getLazySession(), token, callback, userId,
- mContext.getOpPackageName(), sensorId);
+ mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext),
+ mBiometricContext);
scheduleForSensor(sensorId, client);
});
}
@@ -518,13 +550,21 @@
final FaceInternalCleanupClient client =
new FaceInternalCleanupClient(mContext,
mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, enrolledList,
+ mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, enrolledList,
FaceUtils.getInstance(sensorId),
mSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, callback);
});
}
+ private BiometricLogger createLogger(int statsAction, int statsClient) {
+ return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
+ statsAction, statsClient);
+ }
+
@Override
public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
boolean clearSchedulerBuffer) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
index 130a05a..0512017 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
@@ -18,13 +18,14 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
import android.hardware.face.Face;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.RemovalClient;
@@ -44,9 +45,10 @@
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int[] biometricIds, int userId, @NonNull String owner,
@NonNull BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
- authenticatorIds, BiometricsProtoEnums.MODALITY_FACE);
+ logger, biometricContext, authenticatorIds);
mBiometricIds = biometricIds;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index 67bf3f5..de0a36a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.RemoteException;
@@ -26,6 +25,8 @@
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -50,11 +51,11 @@
FaceResetLockoutClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
mLockoutCache = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
index acd2e05..8838345 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
@@ -23,6 +23,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.RevokeChallengeClient;
import java.util.function.Supplier;
@@ -38,8 +40,10 @@
FaceRevokeChallengeClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, int sensorId, long challenge) {
- super(context, lazyDaemon, token, userId, owner, sensorId);
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ long challenge) {
+ super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
mChallenge = challenge;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
index 9d535a2..6c14387 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.IBinder;
@@ -27,6 +26,8 @@
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ErrorConsumer;
@@ -47,11 +48,11 @@
FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId, int feature, boolean enabled,
- byte[] hardwareAuthToken) {
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ int feature, boolean enabled, byte[] hardwareAuthToken) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ logger, biometricContext);
mFeature = feature;
mEnabled = enabled;
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
index f5a98ff..61e7ab7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
@@ -27,6 +27,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.StartUserClient;
@@ -40,9 +42,10 @@
public FaceStartUserClient(@NonNull Context context,
@NonNull Supplier<IFace> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull ISessionCallback sessionCallback,
@NonNull UserStartedCallback<ISession> callback) {
- super(context, lazyDaemon, token, userId, sensorId, callback);
+ super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
mSessionCallback = sessionCallback;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
index 48b4856..0110ae9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
@@ -23,6 +23,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.StopUserClient;
@@ -33,8 +35,9 @@
public FaceStopUserClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull UserStoppedCallback callback) {
- super(context, lazyDaemon, token, userId, sensorId, callback);
+ super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 33e6fa4..b69c760 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -42,12 +42,15 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -88,7 +91,8 @@
@NonNull private final Supplier<AidlSession> mLazySession;
@Nullable private AidlSession mCurrentSession;
- static class HalSessionCallback extends ISessionCallback.Stub {
+ @VisibleForTesting
+ public static class HalSessionCallback extends ISessionCallback.Stub {
/**
* Interface to sends results to the HalSessionCallback's owner.
*/
@@ -472,7 +476,8 @@
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext) {
mTag = tag;
mProvider = provider;
mContext = context;
@@ -487,7 +492,9 @@
@Override
public StopUserClient<?> getStopUserClient(int userId) {
return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
- mSensorProperties.sensorId, () -> mCurrentSession = null);
+ mSensorProperties.sensorId,
+ BiometricLogger.ofUnknown(mContext), biometricContext,
+ () -> mCurrentSession = null);
}
@NonNull
@@ -523,6 +530,7 @@
return new FaceStartUserClient(mContext, provider::getHalInstance,
mToken, newUserId, mSensorProperties.sensorId,
+ BiometricLogger.ofUnknown(mContext), biometricContext,
resultController, userStartedCallback);
}
});
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 586abe2..73c759f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -55,6 +55,8 @@
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
@@ -117,6 +119,7 @@
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
@Nullable private IBiometricsFace mDaemon;
@NonNull private final HalResultController mHalResultController;
+ @NonNull private final BiometricContext mBiometricContext;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
@@ -153,6 +156,7 @@
@NonNull private final LockoutHalImpl mLockoutTracker;
@NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
+
HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
@NonNull BiometricScheduler scheduler, @NonNull LockoutHalImpl lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
@@ -335,12 +339,14 @@
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull Handler handler,
- @NonNull BiometricScheduler scheduler) {
+ @NonNull BiometricScheduler scheduler,
+ @NonNull BiometricContext biometricContext) {
mSensorProperties = sensorProps;
mContext = context;
mSensorId = sensorProps.sensorId;
mScheduler = scheduler;
mHandler = handler;
+ mBiometricContext = biometricContext;
mUsageStats = new UsageStats(context);
mAuthenticatorIds = new HashMap<>();
mLazyDaemon = Face10.this::getDaemon;
@@ -365,7 +371,8 @@
final Handler handler = new Handler(Looper.getMainLooper());
return new Face10(context, sensorProps, lockoutResetDispatcher, handler,
new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
- null /* gestureAvailabilityTracker */));
+ null /* gestureAvailabilityTracker */),
+ BiometricContext.getInstance(context));
}
@Override
@@ -533,7 +540,10 @@
final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, mSensorId, sSystemClock.millis());
+ opPackageName, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, sSystemClock.millis());
mGeneratedChallengeCache = client;
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -562,7 +572,10 @@
mGeneratedChallengeCache = null;
final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
- mLazyDaemon, token, userId, opPackageName, mSensorId);
+ mLazyDaemon, token, userId, opPackageName, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -590,7 +603,10 @@
final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
- ENROLL_TIMEOUT_SEC, previewSurface, mSensorId);
+ ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -637,8 +653,9 @@
final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
mLazyDaemon, token, requestId, receiver, userId, operationId, restricted,
opPackageName, cookie, false /* requireConfirmation */, mSensorId,
- isStrongBiometric, statsClient, mLockoutTracker, mUsageStats,
- allowBackgroundAuthentication, isKeyguardBypassEnabled);
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext, isStrongBiometric, mLockoutTracker,
+ mUsageStats, allowBackgroundAuthentication, isKeyguardBypassEnabled);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -670,7 +687,10 @@
final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
- FaceUtils.getLegacyInstance(mSensorId), mSensorId, mAuthenticatorIds);
+ FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -685,7 +705,10 @@
final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId,
opPackageName,
- FaceUtils.getLegacyInstance(mSensorId), mSensorId, mAuthenticatorIds);
+ FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -702,7 +725,9 @@
final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
- hardwareAuthToken);
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, hardwareAuthToken);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -723,7 +748,9 @@
final int faceId = faces.get(0).getBiometricId();
final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, mSensorId, feature, enabled, hardwareAuthToken, faceId);
+ opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext),
+ mBiometricContext,
+ feature, enabled, hardwareAuthToken, faceId);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -742,7 +769,9 @@
final int faceId = faces.get(0).getBiometricId();
final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
- token, listener, userId, opPackageName, mSensorId, feature, faceId);
+ token, listener, userId, opPackageName, mSensorId,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
+ feature, faceId);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
public void onClientFinished(
@@ -767,7 +796,10 @@
final List<Face> enrolledList = getEnrolledFaces(mSensorId, userId);
final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
- mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, enrolledList,
+ mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, enrolledList,
FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, callback);
});
@@ -890,7 +922,9 @@
final boolean hasEnrolled = !getEnrolledFaces(mSensorId, targetUserId).isEmpty();
final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
- hasEnrolled, mAuthenticatorIds);
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, hasEnrolled, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -904,6 +938,11 @@
});
}
+ private BiometricLogger createLogger(int statsAction, int statsClient) {
+ return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
+ statsAction, statsClient);
+ }
+
/**
* Sends a debug message to the HAL with the provided FileDescriptor and arguments.
*/
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 9038435..8d76e9f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -23,7 +23,6 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.face.FaceManager;
import android.os.IBinder;
@@ -32,6 +31,8 @@
import com.android.internal.R;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -66,12 +67,13 @@
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
- boolean isStrongBiometric, int statsClient, @NonNull LockoutTracker lockoutTracker,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
@NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
boolean isKeyguardBypassEnabled) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
- owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
- BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
+ owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+ isStrongBiometric, null /* taskStackListener */,
lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
isKeyguardBypassEnabled);
setRequestId(requestId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 92f7253..226e458 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -20,7 +20,6 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.Status;
import android.hardware.face.Face;
@@ -32,6 +31,8 @@
import com.android.internal.R;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -58,10 +59,10 @@
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
- @Nullable Surface previewSurface, int sensorId) {
+ @Nullable Surface previewSurface, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
- timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
- false /* shouldVibrate */);
+ timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext);
setRequestId(requestId);
mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
mEnrollIgnoreList = getContext().getResources()
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
index b66ad60..97838a7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
@@ -25,6 +25,8 @@
import android.util.Slog;
import com.android.internal.util.Preconditions;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.GenerateChallengeClient;
@@ -51,8 +53,10 @@
FaceGenerateChallengeClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId, long now) {
- super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, long now) {
+ super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
+ biometricContext);
mCreatedAt = now;
mWaiting = new ArrayList<>();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
index 1b387bf..9812536 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.OptionalBool;
import android.hardware.biometrics.face.V1_0.Status;
@@ -28,6 +27,8 @@
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -48,13 +49,13 @@
FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
@NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId, int feature, int faceId) {
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ int feature, int faceId) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ logger, biometricContext);
mFeature = feature;
mFaceId = faceId;
-
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
index 93a2913..d21a750 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
@@ -18,11 +18,12 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.face.Face;
import android.os.IBinder;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalCleanupClient;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -40,29 +41,32 @@
FaceInternalCleanupClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
- int sensorId, @NonNull List<Face> enrolledList, @NonNull BiometricUtils<Face> utils,
- @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, userId, owner, sensorId, BiometricsProtoEnums.MODALITY_FACE,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList,
+ @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
enrolledList, utils, authenticatorIds);
}
@Override
protected InternalEnumerateClient<IBiometricsFace> getEnumerateClient(Context context,
Supplier<IBiometricsFace> lazyDaemon, IBinder token, int userId, String owner,
- List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId) {
+ List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
- enrolledList, utils, sensorId);
+ enrolledList, utils, sensorId, logger, biometricContext);
}
@Override
protected RemovalClient<Face, IBiometricsFace> getRemovalClient(Context context,
Supplier<IBiometricsFace> lazyDaemon, IBinder token,
int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
Map<Integer, Long> authenticatorIds) {
// Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
// is all done internally.
return new FaceRemovalClient(context, lazyDaemon, token,
null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
- sensorId, authenticatorIds);
+ sensorId, logger, biometricContext, authenticatorIds);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
index f1788de..250dd7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
@@ -18,13 +18,14 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.face.Face;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -41,9 +42,10 @@
FaceInternalEnumerateClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, int userId,
@NonNull String owner, @NonNull List<Face> enrolledList,
- @NonNull BiometricUtils<Face> utils, int sensorId) {
+ @NonNull BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
- BiometricsProtoEnums.MODALITY_FACE);
+ logger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
index cbc23e4..0ee7a35 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
@@ -18,13 +18,14 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.face.Face;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.RemovalClient;
@@ -44,9 +45,11 @@
FaceRemovalClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
- int sensorId, @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
- authenticatorIds, BiometricsProtoEnums.MODALITY_FACE);
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext,
+ @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, logger,
+ biometricContext, authenticatorIds);
mBiometricId = biometricId;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
index 88e2318..6e74d36 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
@@ -18,12 +18,13 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -42,10 +43,10 @@
FaceResetLockoutClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull byte[] hardwareAuthToken) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mHardwareAuthToken = new ArrayList<>();
for (byte b : hardwareAuthToken) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
index ab8d161..b7b0dc04 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
@@ -23,6 +23,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.RevokeChallengeClient;
import java.util.function.Supplier;
@@ -37,8 +39,9 @@
FaceRevokeChallengeClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, int sensorId) {
- super(context, lazyDaemon, token, userId, owner, sensorId);
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
index b2b52e7..3c82f9c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.Status;
import android.os.IBinder;
@@ -26,6 +25,8 @@
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -48,11 +49,11 @@
FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId, int feature, boolean enabled,
- byte[] hardwareAuthToken, int faceId) {
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ int feature, boolean enabled, byte[] hardwareAuthToken, int faceId) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ logger, biometricContext);
mFeature = feature;
mEnabled = enabled;
mFaceId = faceId;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
index 04b9327..8385c3f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
@@ -18,13 +18,14 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.os.Environment;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -41,11 +42,11 @@
FaceUpdateActiveUserClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
- int sensorId, boolean hasEnrolledBiometrics,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
@NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mHasEnrolledBiometrics = hasEnrolledBiometrics;
mAuthenticatorIds = authenticatorIds;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 6366e19..b4befd2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -83,6 +83,7 @@
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -815,7 +816,8 @@
UserHandle.USER_CURRENT) != 0) {
fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
mFingerprintStateCallback, hidlSensor,
- mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+ mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
+ BiometricContext.getInstance(getContext()));
} else {
fingerprint21 = Fingerprint21.newInstance(getContext(),
mFingerprintStateCallback, hidlSensor, mHandler,
@@ -843,7 +845,8 @@
final FingerprintProvider provider =
new FingerprintProvider(getContext(), mFingerprintStateCallback, props,
instance, mLockoutResetDispatcher,
- mGestureAvailabilityDispatcher);
+ mGestureAvailabilityDispatcher,
+ BiometricContext.getInstance(getContext()));
mServiceProviders.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
index 727101a..55861bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -16,11 +16,11 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback;
+
import android.annotation.NonNull;
import android.hardware.biometrics.fingerprint.ISession;
-import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback;
-
/**
* A holder for an AIDL {@link ISession} with additional metadata about the current user
* and the backend.
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 2c1c80c..d26a780 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -23,10 +23,8 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
@@ -35,6 +33,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
import com.android.server.biometrics.log.Probe;
import com.android.server.biometrics.sensors.AuthenticationClient;
@@ -72,15 +72,18 @@
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
- int sensorId, boolean isStrongBiometric, int statsClient,
+ int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric,
@Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner,
- cookie, requireConfirmation, sensorId, isStrongBiometric,
- BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
+ cookie, requireConfirmation, sensorId,
+ biometricLogger, biometricContext,
+ isStrongBiometric, taskStackListener,
lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
@@ -105,7 +108,8 @@
@NonNull
@Override
protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(mALSProbeCallback, callback);
+ return new ClientMonitorCompositeCallback(mALSProbeCallback,
+ getBiometricContextUnsubscriber(), callback);
}
@Override
@@ -175,13 +179,17 @@
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
- final OperationContext context = new OperationContext();
- // TODO: add reason, id, and isAoD
- context.id = 0;
- context.reason = OperationReason.UNKNOWN;
- context.isAoD = false;
- context.isCrypto = isCryptoOperation();
- return session.getSession().authenticateWithContext(mOperationId, context);
+ final OperationContext opContext = getOperationContext();
+ final ICancellationSignal cancel = session.getSession().authenticateWithContext(
+ mOperationId, opContext);
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ });
+ return cancel;
} else {
return session.getSession().authenticate(mOperationId);
}
@@ -190,6 +198,8 @@
@Override
protected void stopHalOperation() {
mSensorOverlays.hide(getSensorId());
+ unsubscribeBiometricContext();
+
if (mCancellationSignal != null) {
try {
mCancellationSignal.cancel();
@@ -219,7 +229,7 @@
context.y = y;
context.minor = minor;
context.major = major;
- context.isAoD = false; // TODO; get value
+ context.isAoD = getBiometricContext().isAoD();
session.getSession().onPointerDownWithContext(context);
} else {
session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
@@ -277,8 +287,8 @@
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
// Lockout metrics are logged as an error code.
final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
- getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
- isCryptoOperation(), getTargetUserId());
+ getLogger().logOnError(getContext(), getOperationContext(),
+ error, 0 /* vendorCode */, getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -296,8 +306,8 @@
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
// Lockout metrics are logged as an error code.
final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
- getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
- isCryptoOperation(), getTargetUserId());
+ getLogger().logOnError(getContext(), getOperationContext(),
+ error, 0 /* vendorCode */, getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 6645332..0e89814 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -20,16 +20,15 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -54,11 +53,10 @@
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId,
- @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric,
- int statsClient) {
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+ @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ true /* shouldVibrate */, biometricLogger, biometricContext);
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
@@ -99,13 +97,7 @@
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
- final OperationContext context = new OperationContext();
- // TODO: add reason, id, and isAoD
- context.id = 0;
- context.reason = OperationReason.UNKNOWN;
- context.isAoD = false;
- context.isCrypto = isCryptoOperation();
- return session.getSession().detectInteractionWithContext(context);
+ return session.getSession().detectInteractionWithContext(getOperationContext());
} else {
return session.getSession().detectInteraction();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index d0c5bb8..e21d901 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -22,10 +22,8 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.common.OperationReason;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
@@ -38,6 +36,10 @@
import android.util.Slog;
import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.CallbackWithProbe;
+import com.android.server.biometrics.log.Probe;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -57,6 +59,7 @@
@NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
@NonNull private final SensorOverlays mSensorOverlays;
+ @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
private final @FingerprintManager.EnrollReason int mEnrollReason;
@Nullable private ICancellationSignal mCancellationSignal;
@@ -68,19 +71,22 @@
@NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
@NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
// UDFPS haptics occur when an image is acquired (instead of when the result is known)
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
- 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
- !sensorProps.isAnyUdfpsType() /* shouldVibrate */);
+ 0 /* timeoutSec */, sensorId,
+ !sensorProps.isAnyUdfpsType() /* shouldVibrate */, logger, biometricContext);
setRequestId(requestId);
mSensorProps = sensorProps;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mMaxTemplatesPerUser = maxTemplatesPerUser;
+ mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */);
+
mEnrollReason = enrollReason;
if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
getLogger().disableMetrics();
@@ -90,8 +96,8 @@
@NonNull
@Override
protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(
- getLogger().createALSCallback(true /* startWithClient */), callback);
+ return new ClientMonitorCompositeCallback(mALSProbeCallback,
+ getBiometricContextUnsubscriber(), callback);
}
@Override
@@ -140,22 +146,6 @@
}
@Override
- protected void stopHalOperation() {
- mSensorOverlays.hide(getSensorId());
-
- if (mCancellationSignal != null) {
- try {
- mCancellationSignal.cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting cancel", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
- }
-
- @Override
protected void startHalOperation() {
mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
@@ -176,22 +166,44 @@
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
if (session.hasContextMethods()) {
- final OperationContext context = new OperationContext();
- // TODO: add reason, id, and isAoD
- context.id = 0;
- context.reason = OperationReason.UNKNOWN;
- context.isAoD = false;
- context.isCrypto = isCryptoOperation();
- return session.getSession().enrollWithContext(hat, context);
+ final OperationContext opContext = getOperationContext();
+ final ICancellationSignal cancel = session.getSession().enrollWithContext(
+ hat, opContext);
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ });
+ return cancel;
} else {
return session.getSession().enroll(hat);
}
}
@Override
+ protected void stopHalOperation() {
+ mSensorOverlays.hide(getSensorId());
+ unsubscribeBiometricContext();
+
+ if (mCancellationSignal != null) {
+ try {
+ mCancellationSignal.cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting cancel", e);
+ onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+ }
+
+ @Override
public void onPointerDown(int x, int y, float minor, float major) {
try {
mIsPointerDown = true;
+ mALSProbeCallback.getProbe().enable();
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
@@ -201,7 +213,7 @@
context.y = y;
context.minor = minor;
context.major = major;
- context.isAoD = false;
+ context.isAoD = getBiometricContext().isAoD();
session.getSession().onPointerDownWithContext(context);
} else {
session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
@@ -215,6 +227,7 @@
public void onPointerUp() {
try {
mIsPointerDown = false;
+ mALSProbeCallback.getProbe().disable();
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index 04a7ca0..ddae8be 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -23,6 +23,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.GenerateChallengeClient;
@@ -38,8 +40,10 @@
@NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener,
- int userId, @NonNull String owner, int sensorId) {
- super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, listener, userId, owner, sensorId,
+ biometricLogger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index 3a487fc..ea1a622 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -18,11 +18,12 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -36,11 +37,12 @@
private final Map<Integer, Long> mAuthenticatorIds;
FingerprintGetAuthenticatorIdClient(@NonNull Context context,
- @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
- int sensorId, Map<Integer, Long> authenticatorIds) {
+ @NonNull Supplier<AidlSession> lazyDaemon,
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+ Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, biometricLogger, biometricContext);
mAuthenticatorIds = authenticatorIds;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index 0ecad72..09bdd6d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -22,6 +22,8 @@
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalCleanupClient;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -39,28 +41,35 @@
class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> {
FingerprintInternalCleanupClient(@NonNull Context context,
- @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
- int sensorId, @NonNull List<Fingerprint> enrolledList,
+ @NonNull Supplier<AidlSession> lazyDaemon,
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull List<Fingerprint> enrolledList,
@NonNull FingerprintUtils utils, @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, userId, owner, sensorId,
- BiometricsProtoEnums.MODALITY_FINGERPRINT, enrolledList, utils, authenticatorIds);
+ super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
+ enrolledList, utils, authenticatorIds);
}
@Override
protected InternalEnumerateClient<AidlSession> getEnumerateClient(Context context,
Supplier<AidlSession> lazyDaemon, IBinder token, int userId, String owner,
- List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId) {
+ List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
- enrolledList, utils, sensorId);
+ enrolledList, utils, sensorId,
+ logger.swapAction(context, BiometricsProtoEnums.ACTION_ENUMERATE),
+ biometricContext);
}
@Override
protected RemovalClient<Fingerprint, AidlSession> getRemovalClient(Context context,
Supplier<AidlSession> lazyDaemon, IBinder token, int biometricId, int userId,
String owner, BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
Map<Integer, Long> authenticatorIds) {
return new FingerprintRemovalClient(context, lazyDaemon, token,
null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
- utils, sensorId, authenticatorIds);
+ utils, sensorId, logger.swapAction(context, BiometricsProtoEnums.ACTION_REMOVE),
+ biometricContext, authenticatorIds);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
index 06ba6d4..a5a832a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
@@ -18,12 +18,13 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -40,9 +41,10 @@
protected FingerprintInternalEnumerateClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId,
@NonNull String owner, @NonNull List<Fingerprint> enrolledList,
- @NonNull BiometricUtils<Fingerprint> utils, int sensorId) {
+ @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
- BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ logger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
index 1ee32e9..bc02897 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
@@ -23,6 +23,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.InvalidationClient;
import java.util.Map;
@@ -33,8 +35,10 @@
public FingerprintInvalidationClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull Map<Integer, Long> authenticatorIds, @NonNull IInvalidationCallback callback) {
- super(context, lazyDaemon, userId, sensorId, authenticatorIds, callback);
+ super(context, lazyDaemon, userId, sensorId, logger, biometricContext,
+ authenticatorIds, callback);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index efc9304..f810bca 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.TypedArray;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
@@ -53,6 +54,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -98,7 +101,7 @@
@NonNull private final BiometricTaskStackListener mTaskStackListener;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
-
+ @NonNull private final BiometricContext mBiometricContext;
@Nullable private IFingerprint mDaemon;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
@@ -141,7 +144,8 @@
@NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull SensorProps[] props, @NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext) {
mContext = context;
mFingerprintStateCallback = fingerprintStateCallback;
mHalInstanceName = halInstanceName;
@@ -150,6 +154,7 @@
mLockoutResetDispatcher = lockoutResetDispatcher;
mActivityTaskManager = ActivityTaskManager.getInstance();
mTaskStackListener = new BiometricTaskStackListener();
+ mBiometricContext = biometricContext;
final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
@@ -181,7 +186,8 @@
location.sensorRadius))
.collect(Collectors.toList()));
final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
- internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher);
+ internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher,
+ mBiometricContext);
mSensors.put(sensorId, sensor);
Slog.d(getTag(), "Added: " + internalProp);
@@ -298,6 +304,9 @@
new FingerprintGetAuthenticatorIdClient(mContext,
mSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client);
});
@@ -307,6 +316,8 @@
mHandler.post(() -> {
final InvalidationRequesterClient<Fingerprint> client =
new InvalidationRequesterClient<>(mContext, userId, sensorId,
+ BiometricLogger.ofUnknown(mContext),
+ mBiometricContext,
FingerprintUtils.getInstance(sensorId));
scheduleForSensor(sensorId, client);
});
@@ -317,7 +328,10 @@
mHandler.post(() -> {
final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
mContext, mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+ mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, hardwareAuthToken,
mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
scheduleForSensor(sensorId, client);
});
@@ -331,7 +345,9 @@
new FingerprintGenerateChallengeClient(mContext,
mSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
- sensorId);
+ sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext);
scheduleForSensor(sensorId, client);
});
}
@@ -343,7 +359,10 @@
final FingerprintRevokeChallengeClient client =
new FingerprintRevokeChallengeClient(mContext,
mSensors.get(sensorId).getLazySession(), token,
- userId, opPackageName, sensorId, challenge);
+ userId, opPackageName, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, challenge);
scheduleForSensor(sensorId, client);
});
}
@@ -361,6 +380,9 @@
mSensors.get(sensorId).getLazySession(), token, id,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@@ -399,8 +421,10 @@
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
mSensors.get(sensorId).getLazySession(), token, id, callback, userId,
- opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
- statsClient);
+ opPackageName, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext,
+ mUdfpsOverlayController, isStrongBiometric);
scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
@@ -417,7 +441,9 @@
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
userId, operationId, restricted, opPackageName, cookie,
- false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+ false /* requireConfirmation */, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext, isStrongBiometric,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
mSensors.get(sensorId).getSensorProperties());
@@ -479,6 +505,9 @@
mSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
@@ -492,14 +521,22 @@
final FingerprintInternalCleanupClient client =
new FingerprintInternalCleanupClient(mContext,
mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, enrolledList,
- FingerprintUtils.getInstance(sensorId),
+ mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
+ enrolledList, FingerprintUtils.getInstance(sensorId),
mSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(callback,
mFingerprintStateCallback));
});
}
+ private BiometricLogger createLogger(int statsAction, int statsClient) {
+ return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
+ statsAction, statsClient);
+ }
+
@Override
public boolean isHardwareDetected(int sensorId) {
return hasHalInstance();
@@ -524,6 +561,9 @@
final FingerprintInvalidationClient client =
new FingerprintInvalidationClient(mContext,
mSensors.get(sensorId).getLazySession(), userId, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds(), callback);
scheduleForSensor(sensorId, client);
});
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
index fbc1dc0..d559bb1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
@@ -19,12 +19,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.RemovalClient;
@@ -45,9 +46,10 @@
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
@Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
@NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
- authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ logger, biometricContext, authenticatorIds);
mBiometricIds = biometricIds;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index 0e64dab..f90cba7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.RemoteException;
@@ -26,6 +25,8 @@
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -50,11 +51,11 @@
FingerprintResetLockoutClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
@NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, biometricLogger, biometricContext);
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
mLockoutCache = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
index fd93867..afa62e2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
@@ -23,6 +23,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.RevokeChallengeClient;
import java.util.function.Supplier;
@@ -38,8 +40,10 @@
FingerprintRevokeChallengeClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, int sensorId, long challenge) {
- super(context, lazyDaemon, token, userId, owner, sensorId);
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ long challenge) {
+ super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
mChallenge = challenge;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
index 9dc06e1..52305a3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
@@ -27,6 +27,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.StartUserClient;
@@ -40,9 +42,10 @@
public FingerprintStartUserClient(@NonNull Context context,
@NonNull Supplier<IFingerprint> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull ISessionCallback sessionCallback,
@NonNull UserStartedCallback<ISession> callback) {
- super(context, lazyDaemon, token, userId, sensorId, callback);
+ super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
mSessionCallback = sessionCallback;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
index fac17f2c..2cc1879 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
@@ -23,6 +23,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.StopUserClient;
@@ -33,8 +35,10 @@
public FingerprintStopUserClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @Nullable IBinder token, int userId,
- int sensorId, @NonNull UserStoppedCallback callback) {
- super(context, lazyDaemon, token, userId, sensorId, callback);
+ int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 2276232..63e345e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -39,12 +39,15 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
@@ -72,7 +75,7 @@
* {@link android.hardware.biometrics.fingerprint.IFingerprint} HAL.
*/
@SuppressWarnings("deprecation")
-class Sensor {
+public class Sensor {
private boolean mTestHalEnabled;
@@ -89,7 +92,8 @@
@Nullable private AidlSession mCurrentSession;
@NonNull private final Supplier<AidlSession> mLazySession;
- static class HalSessionCallback extends ISessionCallback.Stub {
+ @VisibleForTesting
+ public static class HalSessionCallback extends ISessionCallback.Stub {
/**
* Interface to sends results to the HalSessionCallback's owner.
@@ -425,7 +429,8 @@
Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext) {
mTag = tag;
mProvider = provider;
mContext = context;
@@ -442,7 +447,9 @@
@Override
public StopUserClient<?> getStopUserClient(int userId) {
return new FingerprintStopUserClient(mContext, mLazySession, mToken,
- userId, mSensorProperties.sensorId, () -> mCurrentSession = null);
+ userId, mSensorProperties.sensorId,
+ BiometricLogger.ofUnknown(mContext), biometricContext,
+ () -> mCurrentSession = null);
}
@NonNull
@@ -478,6 +485,7 @@
return new FingerprintStartUserClient(mContext, provider::getHalInstance,
mToken, newUserId, mSensorProperties.sensorId,
+ BiometricLogger.ofUnknown(mContext), biometricContext,
resultController, userStartedCallback);
}
});
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 29d460f..9d60859 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -57,6 +57,8 @@
import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
@@ -118,6 +120,7 @@
@NonNull private final HalResultController mHalResultController;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
+ @NonNull private final BiometricContext mBiometricContext;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
@@ -318,15 +321,18 @@
}
}
+ @VisibleForTesting
Fingerprint21(@NonNull Context context,
@NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull BiometricScheduler scheduler,
@NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull HalResultController controller) {
+ @NonNull HalResultController controller,
+ @NonNull BiometricContext biometricContext) {
mContext = context;
mFingerprintStateCallback = fingerprintStateCallback;
+ mBiometricContext = biometricContext;
mSensorProperties = sensorProps;
mSensorId = sensorProps.sensorId;
@@ -368,7 +374,7 @@
final HalResultController controller = new HalResultController(sensorProps.sensorId,
context, handler, scheduler);
return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
- lockoutResetDispatcher, controller);
+ lockoutResetDispatcher, controller, BiometricContext.getInstance(context));
}
@Override
@@ -493,6 +499,9 @@
final FingerprintUpdateActiveUserClient client =
new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
mContext.getOpPackageName(), mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -536,7 +545,10 @@
// thread.
mHandler.post(() -> {
final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
- userId, mContext.getOpPackageName(), sensorId, mLockoutTracker);
+ userId, mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, mLockoutTracker);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -548,7 +560,10 @@
final FingerprintGenerateChallengeClient client =
new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
- mSensorProperties.sensorId);
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -559,7 +574,10 @@
mHandler.post(() -> {
final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
mContext, mLazyDaemon, token, userId, opPackageName,
- mSensorProperties.sensorId);
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -577,7 +595,11 @@
mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
userId, hardwareAuthToken, opPackageName,
FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
- mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
+ mUdfpsOverlayController, mSidefpsController,
enrollReason);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -616,8 +638,10 @@
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
mLazyDaemon, token, id, listener, userId, opPackageName,
- mSensorProperties.sensorId, mUdfpsOverlayController, isStrongBiometric,
- statsClient);
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext, mUdfpsOverlayController,
+ isStrongBiometric);
mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
@@ -636,7 +660,9 @@
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
restricted, opPackageName, cookie, false /* requireConfirmation */,
- mSensorProperties.sensorId, isStrongBiometric, statsClient,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext, isStrongBiometric,
mTaskStackListener, mLockoutTracker,
mUdfpsOverlayController, mSidefpsController,
allowBackgroundAuthentication, mSensorProperties);
@@ -678,7 +704,10 @@
final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
- mSensorProperties.sensorId, mAuthenticatorIds);
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@@ -695,7 +724,10 @@
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver),
0 /* fingerprintId */, userId, opPackageName,
FingerprintUtils.getLegacyInstance(mSensorId),
- mSensorProperties.sensorId, mAuthenticatorIds);
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@@ -709,7 +741,10 @@
mSensorProperties.sensorId, userId);
final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
- mSensorProperties.sensorId, enrolledList,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, enrolledList,
FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, callback);
});
@@ -722,6 +757,11 @@
mFingerprintStateCallback));
}
+ private BiometricLogger createLogger(int statsAction, int statsClient) {
+ return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
+ statsAction, statsClient);
+ }
+
@Override
public boolean isHardwareDetected(int sensorId) {
return getDaemon() != null;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 1694bd9..149526f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -37,6 +37,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.R;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -247,7 +248,8 @@
@NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext) {
Slog.d(TAG, "Creating Fingerprint23Mock!");
final Handler handler = new Handler(Looper.getMainLooper());
@@ -256,7 +258,7 @@
final MockHalResultController controller =
new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
- handler, lockoutResetDispatcher, controller);
+ handler, lockoutResetDispatcher, controller, biometricContext);
}
private static abstract class FakeFingerRunnable implements Runnable {
@@ -385,9 +387,10 @@
@NonNull TestableBiometricScheduler scheduler,
@NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull MockHalResultController controller) {
+ @NonNull MockHalResultController controller,
+ @NonNull BiometricContext biometricContext) {
super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
- lockoutResetDispatcher, controller);
+ lockoutResetDispatcher, controller, biometricContext);
mScheduler = scheduler;
mScheduler.init(this);
mHandler = handler;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 589bfcf..97fbb5f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -23,7 +23,6 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
@@ -32,6 +31,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
import com.android.server.biometrics.log.Probe;
import com.android.server.biometrics.sensors.AuthenticationClient;
@@ -69,7 +70,8 @@
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
- int sensorId, boolean isStrongBiometric, int statsClient,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, boolean isStrongBiometric,
@NonNull TaskStackListener taskStackListener,
@NonNull LockoutFrameworkImpl lockoutTracker,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@@ -77,10 +79,9 @@
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
- owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
- BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
- lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
- false /* isKeyguardBypassEnabled */);
+ owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+ isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
+ true /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutFrameworkImpl = lockoutTracker;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 8848746..c2929d0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -22,7 +22,6 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
@@ -30,6 +29,8 @@
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -59,11 +60,11 @@
@NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId, @Nullable IUdfpsOverlayController udfpsOverlayController,
- boolean isStrongBiometric, int statsClient) {
+ int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+ @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ true /* shouldVibrate */, biometricLogger, biometricContext);
setRequestId(requestId);
mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
mIsStrongBiometric = isStrongBiometric;
@@ -129,8 +130,9 @@
@Override
public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated,
ArrayList<Byte> hardwareAuthToken) {
- getLogger().logOnAuthenticated(getContext(), authenticated, false /* requireConfirmation */,
- isCryptoOperation(), getTargetUserId(), false /* isBiometricPrompt */);
+ getLogger().logOnAuthenticated(getContext(), getOperationContext(),
+ authenticated, false /* requireConfirmation */,
+ getTargetUserId(), false /* isBiometricPrompt */);
// Do not distinguish between success/failures.
vibrateSuccess();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index c69deac..1d478e5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -21,7 +21,6 @@
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
@@ -31,6 +30,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -62,12 +63,13 @@
long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
@NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
@FingerprintManager.EnrollReason int enrollReason) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
- timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
- true /* shouldVibrate */);
+ timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
+ biometricContext);
setRequestId(requestId);
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
index 591f542..3bb7135 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
@@ -23,6 +23,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.GenerateChallengeClient;
@@ -41,8 +43,10 @@
FingerprintGenerateChallengeClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId) {
- super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
+ biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
index 403602b..5e7cf35 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
@@ -18,11 +18,12 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalCleanupClient;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -42,31 +43,35 @@
FingerprintInternalCleanupClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
- @NonNull String owner, int sensorId, @NonNull List<Fingerprint> enrolledList,
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull List<Fingerprint> enrolledList,
@NonNull BiometricUtils<Fingerprint> utils,
@NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, userId, owner, sensorId,
- BiometricsProtoEnums.MODALITY_FINGERPRINT, enrolledList, utils, authenticatorIds);
+ super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
+ enrolledList, utils, authenticatorIds);
}
@Override
protected InternalEnumerateClient<IBiometricsFingerprint> getEnumerateClient(
Context context, Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
int userId, String owner, List<Fingerprint> enrolledList,
- BiometricUtils<Fingerprint> utils, int sensorId) {
+ BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
- enrolledList, utils, sensorId);
+ enrolledList, utils, sensorId, logger, biometricContext);
}
@Override
protected RemovalClient<Fingerprint, IBiometricsFingerprint> getRemovalClient(Context context,
Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
int biometricId, int userId, String owner, BiometricUtils<Fingerprint> utils,
- int sensorId, Map<Integer, Long> authenticatorIds) {
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, Map<Integer, Long> authenticatorIds) {
// Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
// is all done internally.
return new FingerprintRemovalClient(context, lazyDaemon, token,
null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
- sensorId, authenticatorIds);
+ sensorId, logger, biometricContext, authenticatorIds);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
index def8ed0..0840f1b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
@@ -18,13 +18,14 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -42,9 +43,10 @@
FingerprintInternalEnumerateClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
int userId, @NonNull String owner, @NonNull List<Fingerprint> enrolledList,
- @NonNull BiometricUtils<Fingerprint> utils, int sensorId) {
+ @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
- BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ logger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
index 77c201c..9ec56c2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
@@ -18,13 +18,14 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.RemovalClient;
@@ -46,9 +47,10 @@
@NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId,
@NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
- authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ logger, biometricContext, authenticatorIds);
mBiometricId = biometricId;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
index ed28e3f..559ca06 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
@@ -18,9 +18,10 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -33,10 +34,11 @@
@NonNull final LockoutFrameworkImpl mLockoutTracker;
public FingerprintResetLockoutClient(@NonNull Context context, int userId,
- @NonNull String owner, int sensorId, @NonNull LockoutFrameworkImpl lockoutTracker) {
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull LockoutFrameworkImpl lockoutTracker) {
super(context, null /* token */, null /* listener */, userId, owner, 0 /* cookie */,
- sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ sensorId, logger, biometricContext);
mLockoutTracker = lockoutTracker;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
index 0180a46..6273417 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
@@ -23,6 +23,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.RevokeChallengeClient;
import java.util.function.Supplier;
@@ -39,8 +41,9 @@
FingerprintRevokeChallengeClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, int sensorId) {
- super(context, lazyDaemon, token, userId, owner, sensorId);
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index cb9c33e..a4e6025 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.os.Build;
import android.os.Environment;
@@ -27,6 +26,8 @@
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -50,12 +51,13 @@
FingerprintUpdateActiveUserClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
- @NonNull String owner, int sensorId, Supplier<Integer> currentUserId,
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ Supplier<Integer> currentUserId,
boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
boolean forceUpdateAuthenticatorId) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mCurrentUserId = currentUserId;
mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
mHasEnrolledBiometrics = hasEnrolledBiometrics;
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 1e00ea9..1b0341c 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -308,7 +308,8 @@
public void onFixedRotationFinished(int displayId) { }
@Override
- public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearArea) { }
+ public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+ List<Rect> unrestricted) { }
}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 28508f4..53fe450 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -1090,6 +1090,10 @@
&& mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId)) {
return;
}
+ if (mPm.checkPermission(Manifest.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION,
+ callingPackage) == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
// Don't notify if already notified for this uid and clip.
if (clipboard.mNotifiedUids.get(uid)) {
return;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index c2ca3a5..d0e39cc 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -23,7 +23,6 @@
import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
-import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -348,7 +347,7 @@
mBaseState = Optional.of(baseState);
if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
- mOverrideRequestController.cancelOverrideRequests();
+ mOverrideRequestController.cancelOverrideRequest();
}
mOverrideRequestController.handleBaseStateChanged();
updatePendingStateLocked();
@@ -503,7 +502,7 @@
@OverrideRequestController.RequestStatus int status) {
if (status == STATUS_ACTIVE) {
mActiveOverride = Optional.of(request);
- } else if (status == STATUS_SUSPENDED || status == STATUS_CANCELED) {
+ } else if (status == STATUS_CANCELED) {
if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
mActiveOverride = Optional.empty();
}
@@ -528,8 +527,6 @@
// Schedule the notification now.
processRecord.notifyRequestActiveAsync(request.getToken());
}
- } else if (status == STATUS_SUSPENDED) {
- processRecord.notifyRequestSuspendedAsync(request.getToken());
} else {
processRecord.notifyRequestCanceledAsync(request.getToken());
}
@@ -595,15 +592,14 @@
}
}
- private void cancelRequestInternal(int callingPid, @NonNull IBinder token) {
+ private void cancelStateRequestInternal(int callingPid) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
if (processRecord == null) {
throw new IllegalStateException("Process " + callingPid
+ " has no registered callback.");
}
-
- mOverrideRequestController.cancelRequest(token);
+ mActiveOverride.ifPresent(mOverrideRequestController::cancelRequest);
}
}
@@ -628,6 +624,23 @@
}
}
+ /**
+ * Allow top processes to request or cancel a device state change. If the calling process ID is
+ * not the top app, then check if this process holds the CONTROL_DEVICE_STATE permission.
+ * @param callingPid
+ */
+ private void checkCanControlDeviceState(int callingPid) {
+ // Allow top processes to request a device state change
+ // If the calling process ID is not the top app, then we check if this process
+ // holds a permission to CONTROL_DEVICE_STATE
+ final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
+ if (topApp == null || topApp.getPid() != callingPid) {
+ getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+ "Permission required to request device state, "
+ + "or the call must come from the top focused app.");
+ }
+ }
+
private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
@Override
public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
@@ -716,24 +729,6 @@
});
}
- public void notifyRequestSuspendedAsync(IBinder token) {
- @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
- if (lastStatus != null
- && (lastStatus == STATUS_SUSPENDED || lastStatus == STATUS_CANCELED)) {
- return;
- }
-
- mLastNotifiedStatus.put(token, STATUS_SUSPENDED);
- mHandler.post(() -> {
- try {
- mCallback.onRequestSuspended(token);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
- ex);
- }
- });
- }
-
public void notifyRequestCanceledAsync(IBinder token) {
@RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
if (lastStatus != null && lastStatus == STATUS_CANCELED) {
@@ -782,12 +777,7 @@
// Allow top processes to request a device state change
// If the calling process ID is not the top app, then we check if this process
// holds a permission to CONTROL_DEVICE_STATE
- final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
- if (topApp.getPid() != callingPid) {
- getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
- "Permission required to request device state, "
- + "or the call must come from the top focused app.");
- }
+ checkCanControlDeviceState(callingPid);
if (token == null) {
throw new IllegalArgumentException("Request token must not be null.");
@@ -802,25 +792,16 @@
}
@Override // Binder call
- public void cancelRequest(IBinder token) {
+ public void cancelStateRequest() {
final int callingPid = Binder.getCallingPid();
// Allow top processes to cancel a device state change
// If the calling process ID is not the top app, then we check if this process
// holds a permission to CONTROL_DEVICE_STATE
- final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
- if (topApp.getPid() != callingPid) {
- getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
- "Permission required to cancel device state, "
- + "or the call must come from the top focused app.");
- }
-
- if (token == null) {
- throw new IllegalArgumentException("Request token must not be null.");
- }
+ checkCanControlDeviceState(callingPid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
- cancelRequestInternal(callingPid, token);
+ cancelStateRequestInternal(callingPid);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index eed68f8..659ee75 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -97,7 +97,7 @@
try {
if ("reset".equals(nextArg)) {
if (sLastRequest != null) {
- mClient.cancelRequest(sLastRequest);
+ mClient.cancelStateRequest();
sLastRequest = null;
}
} else {
@@ -105,9 +105,6 @@
DeviceStateRequest request = DeviceStateRequest.newBuilder(requestedState).build();
mClient.requestState(request, null /* executor */, null /* callback */);
- if (sLastRequest != null) {
- mClient.cancelRequest(sLastRequest);
- }
sLastRequest = request;
}
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index 36cb416..01f5a09 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -18,15 +18,13 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.IBinder;
+import android.util.Slog;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
/**
* Manages the lifecycle of override requests.
@@ -36,29 +34,26 @@
* <ul>
* <li>A new request is added with {@link #addRequest(OverrideRequest)}, in which case the
* request will become suspended.</li>
- * <li>The request is cancelled with {@link #cancelRequest(IBinder)} or as a side effect
+ * <li>The request is cancelled with {@link #cancelRequest} or as a side effect
* of other methods calls, such as {@link #handleProcessDied(int)}.</li>
* </ul>
*/
final class OverrideRequestController {
+ private static final String TAG = "OverrideRequestController";
+
static final int STATUS_UNKNOWN = 0;
/**
* The request is the top-most request.
*/
static final int STATUS_ACTIVE = 1;
/**
- * The request is still present but is being superseded by another request.
- */
- static final int STATUS_SUSPENDED = 2;
- /**
* The request is not longer valid.
*/
- static final int STATUS_CANCELED = 3;
+ static final int STATUS_CANCELED = 2;
@IntDef(prefix = {"STATUS_"}, value = {
STATUS_UNKNOWN,
STATUS_ACTIVE,
- STATUS_SUSPENDED,
STATUS_CANCELED
})
@Retention(RetentionPolicy.SOURCE)
@@ -68,8 +63,6 @@
switch (status) {
case STATUS_ACTIVE:
return "ACTIVE";
- case STATUS_SUSPENDED:
- return "SUSPENDED";
case STATUS_CANCELED:
return "CANCELED";
case STATUS_UNKNOWN:
@@ -79,15 +72,13 @@
}
private final StatusChangeListener mListener;
- private final List<OverrideRequest> mTmpRequestsToCancel = new ArrayList<>();
- // List of override requests with the most recent override request at the end.
- private final ArrayList<OverrideRequest> mRequests = new ArrayList<>();
+ // Handle to the current override request, null if none.
+ private OverrideRequest mRequest;
private boolean mStickyRequestsAllowed;
- // List of override requests that have outlived their process and will only be cancelled through
- // a call to cancelStickyRequests().
- private final ArrayList<OverrideRequest> mStickyRequests = new ArrayList<>();
+ // The current request has outlived their process.
+ private boolean mStickyRequest;
OverrideRequestController(@NonNull StatusChangeListener listener) {
mListener = listener;
@@ -97,26 +88,26 @@
* Sets sticky requests as either allowed or disallowed. When sticky requests are allowed a call
* to {@link #handleProcessDied(int)} will not result in the request being cancelled
* immediately. Instead, the request will be marked sticky and must be cancelled with a call
- * to {@link #cancelStickyRequests()}.
+ * to {@link #cancelStickyRequest()}.
*/
void setStickyRequestsAllowed(boolean stickyRequestsAllowed) {
mStickyRequestsAllowed = stickyRequestsAllowed;
if (!mStickyRequestsAllowed) {
- cancelStickyRequests();
+ cancelStickyRequest();
}
}
/**
- * Adds a request to the top of the stack and notifies the listener of all changes to request
- * status as a result of this operation.
+ * Sets the new request as active and cancels the previous override request, notifies the
+ * listener of all changes to request status as a result of this operation.
*/
void addRequest(@NonNull OverrideRequest request) {
- mRequests.add(request);
+ OverrideRequest previousRequest = mRequest;
+ mRequest = request;
mListener.onStatusChanged(request, STATUS_ACTIVE);
- if (mRequests.size() > 1) {
- OverrideRequest prevRequest = mRequests.get(mRequests.size() - 2);
- mListener.onStatusChanged(prevRequest, STATUS_SUSPENDED);
+ if (previousRequest != null) {
+ cancelRequestLocked(previousRequest);
}
}
@@ -124,42 +115,32 @@
* Cancels the request with the specified {@code token} and notifies the listener of all changes
* to request status as a result of this operation.
*/
- void cancelRequest(@NonNull IBinder token) {
- int index = getRequestIndex(token);
- if (index == -1) {
+ void cancelRequest(@NonNull OverrideRequest request) {
+ // Either don't have a current request or attempting to cancel an already cancelled request
+ if (!hasRequest(request.getToken())) {
return;
}
-
- OverrideRequest request = mRequests.remove(index);
- if (index == mRequests.size() && mRequests.size() > 0) {
- // We removed the current active request so we need to set the new active request
- // before cancelling this request.
- OverrideRequest newTop = getLast(mRequests);
- mListener.onStatusChanged(newTop, STATUS_ACTIVE);
- }
- mListener.onStatusChanged(request, STATUS_CANCELED);
+ cancelCurrentRequestLocked();
}
/**
- * Cancels all requests that are currently marked sticky and notifies the listener of all
+ * Cancels a request that is currently marked sticky and notifies the listener of all
* changes to request status as a result of this operation.
*
* @see #setStickyRequestsAllowed(boolean)
*/
- void cancelStickyRequests() {
- mTmpRequestsToCancel.clear();
- mTmpRequestsToCancel.addAll(mStickyRequests);
- cancelRequestsLocked(mTmpRequestsToCancel);
+ void cancelStickyRequest() {
+ if (mStickyRequest) {
+ cancelCurrentRequestLocked();
+ }
}
/**
- * Cancels all override requests, this could be due to the device being put
+ * Cancels the current override request, this could be due to the device being put
* into a hardware state that declares the flag "FLAG_CANCEL_OVERRIDE_REQUESTS"
*/
- void cancelOverrideRequests() {
- mTmpRequestsToCancel.clear();
- mTmpRequestsToCancel.addAll(mRequests);
- cancelRequestsLocked(mTmpRequestsToCancel);
+ void cancelOverrideRequest() {
+ cancelCurrentRequestLocked();
}
/**
@@ -167,7 +148,7 @@
* {@code token}, {@code false} otherwise.
*/
boolean hasRequest(@NonNull IBinder token) {
- return getRequestIndex(token) != -1;
+ return mRequest != null && token == mRequest.getToken();
}
/**
@@ -176,139 +157,79 @@
* operation.
*/
void handleProcessDied(int pid) {
- if (mRequests.isEmpty()) {
+ if (mRequest == null) {
return;
}
- mTmpRequestsToCancel.clear();
- OverrideRequest prevActiveRequest = getLast(mRequests);
- for (OverrideRequest request : mRequests) {
- if (request.getPid() == pid) {
- mTmpRequestsToCancel.add(request);
+ if (mRequest.getPid() == pid) {
+ if (mStickyRequestsAllowed) {
+ // Do not cancel the requests now because sticky requests are allowed. These
+ // requests will be cancelled on a call to cancelStickyRequests().
+ mStickyRequest = true;
+ return;
}
+ cancelCurrentRequestLocked();
}
-
- if (mStickyRequestsAllowed) {
- // Do not cancel the requests now because sticky requests are allowed. These
- // requests will be cancelled on a call to cancelStickyRequests().
- mStickyRequests.addAll(mTmpRequestsToCancel);
- return;
- }
-
- cancelRequestsLocked(mTmpRequestsToCancel);
}
/**
* Notifies the controller that the base state has changed. The controller will notify the
* listener of all changes to request status as a result of this change.
- *
- * @return {@code true} if calling this method has lead to a new active request, {@code false}
- * otherwise.
*/
- boolean handleBaseStateChanged() {
- if (mRequests.isEmpty()) {
- return false;
+ void handleBaseStateChanged() {
+ if (mRequest == null) {
+ return;
}
- mTmpRequestsToCancel.clear();
- OverrideRequest prevActiveRequest = getLast(mRequests);
- for (int i = 0; i < mRequests.size(); i++) {
- OverrideRequest request = mRequests.get(i);
- if ((request.getFlags() & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) {
- mTmpRequestsToCancel.add(request);
- }
+ if ((mRequest.getFlags()
+ & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) {
+ cancelCurrentRequestLocked();
}
-
- final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
- return newActiveRequest;
}
/**
* Notifies the controller that the set of supported states has changed. The controller will
* notify the listener of all changes to request status as a result of this change.
- *
- * @return {@code true} if calling this method has lead to a new active request, {@code false}
- * otherwise.
*/
- boolean handleNewSupportedStates(int[] newSupportedStates) {
- if (mRequests.isEmpty()) {
- return false;
+ void handleNewSupportedStates(int[] newSupportedStates) {
+ if (mRequest == null) {
+ return;
}
- mTmpRequestsToCancel.clear();
- for (int i = 0; i < mRequests.size(); i++) {
- OverrideRequest request = mRequests.get(i);
- if (!contains(newSupportedStates, request.getRequestedState())) {
- mTmpRequestsToCancel.add(request);
- }
+ if (!contains(newSupportedStates, mRequest.getRequestedState())) {
+ cancelCurrentRequestLocked();
}
-
- final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
- return newActiveRequest;
}
void dumpInternal(PrintWriter pw) {
- final int requestCount = mRequests.size();
+ OverrideRequest overrideRequest = mRequest;
+ final boolean requestActive = overrideRequest != null;
pw.println();
- pw.println("Override requests: size=" + requestCount);
- for (int i = 0; i < requestCount; i++) {
- OverrideRequest overrideRequest = mRequests.get(i);
- int status = (i == requestCount - 1) ? STATUS_ACTIVE : STATUS_SUSPENDED;
- pw.println(" " + i + ": mPid=" + overrideRequest.getPid()
+ pw.println("Override Request active: " + requestActive);
+ if (requestActive) {
+ pw.println("Request: mPid=" + overrideRequest.getPid()
+ ", mRequestedState=" + overrideRequest.getRequestedState()
+ ", mFlags=" + overrideRequest.getFlags()
- + ", mStatus=" + statusToString(status));
+ + ", mStatus=" + statusToString(STATUS_ACTIVE));
}
}
+ private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel) {
+ mListener.onStatusChanged(requestToCancel, STATUS_CANCELED);
+ }
+
/**
- * Handles cancelling a set of requests. If the set of requests to cancel will lead to a new
- * request becoming active this request will also be notified of its change in state.
- *
- * @return {@code true} if calling this method has lead to a new active request, {@code false}
- * otherwise.
+ * Handles cancelling {@code mRequest}.
+ * Notifies the listener of the canceled status as well.
*/
- private boolean cancelRequestsLocked(List<OverrideRequest> requestsToCancel) {
- if (requestsToCancel.isEmpty()) {
- return false;
+ private void cancelCurrentRequestLocked() {
+ if (mRequest == null) {
+ Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
+ return;
}
-
- OverrideRequest prevActiveRequest = getLast(mRequests);
- boolean causedNewRequestToBecomeActive = false;
- mRequests.removeAll(requestsToCancel);
- mStickyRequests.removeAll(requestsToCancel);
- if (!mRequests.isEmpty()) {
- OverrideRequest newActiveRequest = getLast(mRequests);
- if (newActiveRequest != prevActiveRequest) {
- mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
- causedNewRequestToBecomeActive = true;
- }
- }
-
- for (int i = 0; i < requestsToCancel.size(); i++) {
- mListener.onStatusChanged(requestsToCancel.get(i), STATUS_CANCELED);
- }
- return causedNewRequestToBecomeActive;
- }
-
- private int getRequestIndex(@NonNull IBinder token) {
- final int numberOfRequests = mRequests.size();
- if (numberOfRequests == 0) {
- return -1;
- }
-
- for (int i = 0; i < numberOfRequests; i++) {
- OverrideRequest request = mRequests.get(i);
- if (request.getToken() == token) {
- return i;
- }
- }
- return -1;
- }
-
- @Nullable
- private static <T> T getLast(List<T> list) {
- return list.size() > 0 ? list.get(list.size() - 1) : null;
+ mStickyRequest = false;
+ mListener.onStatusChanged(mRequest, STATUS_CANCELED);
+ mRequest = null;
}
private static boolean contains(int[] array, int value) {
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 1b55257..162eb0e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -201,6 +201,10 @@
// Controls High Brightness Mode.
private HighBrightnessModeController mHbmController;
+ // Throttles (caps) maximum allowed brightness
+ private BrightnessThrottler mBrightnessThrottler;
+ private boolean mIsBrightnessThrottled;
+
// Context-sensitive brightness configurations require keeping track of the foreground app's
// package name and category, which is done by registering a TaskStackListener to call back to
// us onTaskStackChanged, and then using the ActivityTaskManager to get the foreground app's
@@ -226,7 +230,7 @@
long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds, Context context,
- HighBrightnessModeController hbmController,
+ HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong) {
this(new Injector(), callbacks, looper, sensorManager, lightSensor,
@@ -235,8 +239,8 @@
lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
ambientBrightnessThresholds, screenBrightnessThresholds, context,
- hbmController, idleModeBrightnessMapper, ambientLightHorizonShort,
- ambientLightHorizonLong
+ hbmController, brightnessThrottler, idleModeBrightnessMapper,
+ ambientLightHorizonShort, ambientLightHorizonLong
);
}
@@ -249,7 +253,7 @@
long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds, Context context,
- HighBrightnessModeController hbmController,
+ HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong) {
mInjector = injector;
@@ -291,6 +295,7 @@
mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
mHbmController = hbmController;
+ mBrightnessThrottler = brightnessThrottler;
mInteractiveModeBrightnessMapper = interactiveModeBrightnessMapper;
mIdleModeBrightnessMapper = idleModeBrightnessMapper;
// Initialize to active (normal) screen brightness mode
@@ -365,6 +370,13 @@
prepareBrightnessAdjustmentSample();
}
changed |= setLightSensorEnabled(enable && !dozing);
+
+ if (mIsBrightnessThrottled != mBrightnessThrottler.isThrottled()) {
+ // Maximum brightness has changed, so recalculate display brightness.
+ mIsBrightnessThrottled = mBrightnessThrottler.isThrottled();
+ changed = true;
+ }
+
if (changed) {
updateAutoBrightness(false /*sendUpdate*/, userInitiatedChange);
}
@@ -855,8 +867,11 @@
// Clamps values with float range [0.0-1.0]
private float clampScreenBrightness(float value) {
- return MathUtils.constrain(value,
- mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
+ final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+ mBrightnessThrottler.getBrightnessCap());
+ final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+ mBrightnessThrottler.getBrightnessCap());
+ return MathUtils.constrain(value, minBrightness, maxBrightness);
}
private void prepareBrightnessAdjustmentSample() {
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index d0ce9ef..5de162c 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 @@
}
/**
+ * 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 4e88acd..7f1482e 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 @@
}
}
+ 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 @@
}
}
+ 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 @@
}
@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 a9875c8..bfdac57 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 @@
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 @@
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 @@
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/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1f44854..6ae1a5a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -974,7 +974,7 @@
lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
ambientBrightnessThresholds, screenBrightnessThresholds, mContext,
- mHbmController, mIdleModeBrightnessMapper,
+ mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
mDisplayDeviceConfig.getAmbientHorizonShort(),
mDisplayDeviceConfig.getAmbientHorizonLong());
} else {
@@ -1014,6 +1014,9 @@
mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
}
}
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+ }
}
private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@@ -1344,6 +1347,29 @@
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
}
+ // Now that a desired brightness has been calculated, apply brightness throttling. The
+ // dimming and low power transformations that follow can only dim brightness further.
+ //
+ // We didn't do this earlier through brightness clamping because we need to know both
+ // unthrottled (unclamped/ideal) and throttled brightness levels for subsequent operations.
+ // Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
+ // we broadcast this change through setting.
+ final float unthrottledBrightnessState = brightnessState;
+ if (mBrightnessThrottler.isThrottled()) {
+ brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
+ mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED);
+ if (!mAppliedThrottling) {
+ // Brightness throttling is needed, so do so quickly.
+ // Later, when throttling is removed, we let other mechanisms decide on speed.
+ slowChange = false;
+ updateScreenBrightnessSetting = true;
+ }
+ mAppliedThrottling = true;
+ } else if (mAppliedThrottling) {
+ mAppliedThrottling = false;
+ updateScreenBrightnessSetting = true;
+ }
+
if (updateScreenBrightnessSetting) {
// Tell the rest of the system about the new brightness in case we had to change it
// for things like auto-brightness or high-brightness-mode. Note that we do this
@@ -1390,20 +1416,6 @@
mAppliedLowPower = false;
}
- // Apply brightness throttling after applying all other transforms
- final float unthrottledBrightnessState = brightnessState;
- if (mBrightnessThrottler.isThrottled()) {
- brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
- mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED);
- if (!mAppliedThrottling) {
- slowChange = false;
- }
- mAppliedThrottling = true;
- } else if (mAppliedThrottling) {
- slowChange = false;
- mAppliedThrottling = false;
- }
-
// The current brightness to use has been calculated at this point, and HbmController should
// be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
// here instead of having HbmController listen to the brightness setting because certain
@@ -1653,6 +1665,10 @@
private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
synchronized (mCachedBrightnessInfo) {
+ final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+ mBrightnessThrottler.getBrightnessCap());
+ final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+ mBrightnessThrottler.getBrightnessCap());
boolean changed = false;
changed |=
@@ -1663,10 +1679,10 @@
adjustedBrightness);
changed |=
mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin,
- mHbmController.getCurrentBrightnessMin());
+ minBrightness);
changed |=
mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax,
- mHbmController.getCurrentBrightnessMax());
+ maxBrightness);
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
mHbmController.getHighBrightnessMode());
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 534ed5d..23c17f5 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -521,6 +521,10 @@
} else if (mIsBlockedByLowPowerMode) {
reason = FrameworkStatsLog
.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_BATTERY_SAVE_ON;
+ } else if (mBrightness <= mHbmData.transitionPoint) {
+ // This must be after external thermal check.
+ reason = FrameworkStatsLog
+ .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS;
}
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 3a9ef0a..a31c231 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 @@
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 @@
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 @@
mBacklightAdapter = new BacklightAdapter(displayToken, isDefaultDisplay,
mSurfaceControlProxy);
mDisplayDeviceConfig = null;
+ mActiveDisplayModeAtStartId = dynamicInfo.activeDisplayModeId;
}
@Override
@@ -238,12 +243,23 @@
}
/**
+ * 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 @@
// 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 @@
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 @@
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 @@
}
@Override
+ public Display.Mode getSystemPreferredDisplayModeLocked() {
+ return findMode(mSystemPreferredModeId);
+ }
+
+ @Override
public void setRequestedColorModeLocked(int colorMode) {
requestColorModeLocked(colorMode);
}
@@ -1072,6 +1115,17 @@
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 @@
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 @@
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 1ebd1f5a..d8672fc 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 @@
mRate = 0;
mTargetValue = target;
mCurrentValue = target;
- mProperty.setValue(mObject, target);
+ setPropertyValue(target);
if (mAnimating) {
mAnimating = false;
cancelAnimationCallback();
@@ -125,6 +125,15 @@
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 @@
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/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 151ec81..fb36dc7 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -44,26 +44,18 @@
AmbientSensor.AmbientBrightnessSensor.Callbacks,
AmbientSensor.AmbientColorTemperatureSensor.Callbacks {
- protected static final String TAG = "DisplayWhiteBalanceController";
- protected boolean mLoggingEnabled;
+ private static final String TAG = "DisplayWhiteBalanceController";
+ private boolean mLoggingEnabled;
- private boolean mEnabled;
+ private final ColorDisplayServiceInternal mColorDisplayServiceInternal;
- // To decouple the DisplayPowerController from the DisplayWhiteBalanceController, the DPC
- // implements Callbacks and passes itself to the DWBC so it can call back into it without
- // knowing about it.
- private Callbacks mCallbacks;
-
- private AmbientSensor.AmbientBrightnessSensor mBrightnessSensor;
-
+ private final AmbientSensor.AmbientBrightnessSensor mBrightnessSensor;
@VisibleForTesting
AmbientFilter mBrightnessFilter;
- private AmbientSensor.AmbientColorTemperatureSensor mColorTemperatureSensor;
-
+ private final AmbientSensor.AmbientColorTemperatureSensor mColorTemperatureSensor;
@VisibleForTesting
AmbientFilter mColorTemperatureFilter;
- private DisplayWhiteBalanceThrottler mThrottler;
-
+ private final DisplayWhiteBalanceThrottler mThrottler;
// In low brightness conditions the ALS readings are more noisy and produce
// high errors. This default is introduced to provide a fixed display color
// temperature when sensor readings become unreliable.
@@ -74,16 +66,12 @@
private final float mHighLightAmbientColorTemperature;
private float mAmbientColorTemperature;
-
@VisibleForTesting
float mPendingAmbientColorTemperature;
private float mLastAmbientColorTemperature;
- private ColorDisplayServiceInternal mColorDisplayServiceInternal;
-
// The most recent ambient color temperature values are kept for debugging purposes.
- private static final int HISTORY_SIZE = 50;
- private History mAmbientColorTemperatureHistory;
+ private final History mAmbientColorTemperatureHistory;
// Override the ambient color temperature for debugging purposes.
private float mAmbientColorTemperatureOverride;
@@ -91,6 +79,10 @@
// A piecewise linear relationship between ambient and display color temperatures.
private Spline.LinearSpline mAmbientToDisplayColorTemperatureSpline;
+ // A piecewise linear relationship between ambient and display color temperatures, with a
+ // stronger change between the two sets of values.
+ private Spline.LinearSpline mStrongAmbientToDisplayColorTemperatureSpline;
+
// In very low or very high brightness conditions Display White Balance should
// be to set to a default instead of using mAmbientToDisplayColorTemperatureSpline.
// However, setting Display White Balance based on thresholds can cause the
@@ -109,6 +101,17 @@
private float mLatestLowLightBias;
private float mLatestHighLightBias;
+ private boolean mEnabled;
+
+ // Whether a higher-strength adjustment should be applied; this must be enabled in addition to
+ // mEnabled in order to be applied.
+ private boolean mStrongModeEnabled;
+
+ // To decouple the DisplayPowerController from the DisplayWhiteBalanceController, the DPC
+ // implements Callbacks and passes itself to the DWBC so it can call back into it without
+ // knowing about it.
+ private Callbacks mDisplayPowerControllerCallbacks;
+
/**
* @param brightnessSensor
* The sensor used to detect changes in the ambient brightness.
@@ -159,16 +162,18 @@
@NonNull AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor,
@NonNull AmbientFilter colorTemperatureFilter,
@NonNull DisplayWhiteBalanceThrottler throttler,
- float[] lowLightAmbientBrightnesses, float[] lowLightAmbientBiases,
+ float[] lowLightAmbientBrightnesses,
+ float[] lowLightAmbientBiases,
float lowLightAmbientColorTemperature,
- float[] highLightAmbientBrightnesses, float[] highLightAmbientBiases,
+ float[] highLightAmbientBrightnesses,
+ float[] highLightAmbientBiases,
float highLightAmbientColorTemperature,
- float[] ambientColorTemperatures, float[] displayColorTemperatures) {
+ float[] ambientColorTemperatures,
+ float[] displayColorTemperatures,
+ float[] strongAmbientColorTemperatures,
+ float[] strongDisplayColorTemperatures) {
validateArguments(brightnessSensor, brightnessFilter, colorTemperatureSensor,
colorTemperatureFilter, throttler);
- mLoggingEnabled = false;
- mEnabled = false;
- mCallbacks = null;
mBrightnessSensor = brightnessSensor;
mBrightnessFilter = brightnessFilter;
mColorTemperatureSensor = colorTemperatureSensor;
@@ -179,7 +184,7 @@
mAmbientColorTemperature = -1.0f;
mPendingAmbientColorTemperature = -1.0f;
mLastAmbientColorTemperature = -1.0f;
- mAmbientColorTemperatureHistory = new History(HISTORY_SIZE);
+ mAmbientColorTemperatureHistory = new History(/* size= */ 50);
mAmbientColorTemperatureOverride = -1.0f;
try {
@@ -235,6 +240,13 @@
mAmbientToDisplayColorTemperatureSpline = null;
}
+ try {
+ mStrongAmbientToDisplayColorTemperatureSpline = new Spline.LinearSpline(
+ strongAmbientColorTemperatures, strongDisplayColorTemperatures);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to create strong ambient to display color temperature spline", e);
+ }
+
mColorDisplayServiceInternal = LocalServices.getService(ColorDisplayServiceInternal.class);
}
@@ -255,6 +267,19 @@
}
/**
+ * Enable/disable the stronger adjustment option.
+ *
+ * @param enabled whether the stronger adjustment option should be turned on
+ */
+ public void setStrongModeEnabled(boolean enabled) {
+ mStrongModeEnabled = enabled;
+ if (mEnabled) {
+ updateAmbientColorTemperature();
+ updateDisplayColorTemperature();
+ }
+ }
+
+ /**
* Set an object to call back to when the display color temperature should be updated.
*
* @param callbacks
@@ -263,10 +288,10 @@
* @return Whether the method succeeded or not.
*/
public boolean setCallbacks(Callbacks callbacks) {
- if (mCallbacks == callbacks) {
+ if (mDisplayPowerControllerCallbacks == callbacks) {
return false;
}
- mCallbacks = callbacks;
+ mDisplayPowerControllerCallbacks = callbacks;
return true;
}
@@ -321,7 +346,7 @@
writer.println("DisplayWhiteBalanceController");
writer.println(" mLoggingEnabled=" + mLoggingEnabled);
writer.println(" mEnabled=" + mEnabled);
- writer.println(" mCallbacks=" + mCallbacks);
+ writer.println(" mDisplayPowerControllerCallbacks=" + mDisplayPowerControllerCallbacks);
mBrightnessSensor.dump(writer);
mBrightnessFilter.dump(writer);
mColorTemperatureSensor.dump(writer);
@@ -336,6 +361,8 @@
writer.println(" mAmbientColorTemperatureOverride=" + mAmbientColorTemperatureOverride);
writer.println(" mAmbientToDisplayColorTemperatureSpline="
+ mAmbientToDisplayColorTemperatureSpline);
+ writer.println(" mStrongAmbientToDisplayColorTemperatureSpline="
+ + mStrongAmbientToDisplayColorTemperatureSpline);
writer.println(" mLowLightAmbientBrightnessToBiasSpline="
+ mLowLightAmbientBrightnessToBiasSpline);
writer.println(" mHighLightAmbientBrightnessToBiasSpline="
@@ -364,9 +391,20 @@
float ambientColorTemperature = mColorTemperatureFilter.getEstimate(time);
mLatestAmbientColorTemperature = ambientColorTemperature;
- if (mAmbientToDisplayColorTemperatureSpline != null && ambientColorTemperature != -1.0f) {
- ambientColorTemperature =
- mAmbientToDisplayColorTemperatureSpline.interpolate(ambientColorTemperature);
+ if (mStrongModeEnabled) {
+ if (mStrongAmbientToDisplayColorTemperatureSpline != null
+ && ambientColorTemperature != -1.0f) {
+ ambientColorTemperature =
+ mStrongAmbientToDisplayColorTemperatureSpline.interpolate(
+ ambientColorTemperature);
+ }
+ } else {
+ if (mAmbientToDisplayColorTemperatureSpline != null
+ && ambientColorTemperature != -1.0f) {
+ ambientColorTemperature =
+ mAmbientToDisplayColorTemperatureSpline.interpolate(
+ ambientColorTemperature);
+ }
}
float ambientBrightness = mBrightnessFilter.getEstimate(time);
@@ -409,8 +447,8 @@
Slog.d(TAG, "pending ambient color temperature: " + ambientColorTemperature);
}
mPendingAmbientColorTemperature = ambientColorTemperature;
- if (mCallbacks != null) {
- mCallbacks.updateWhiteBalance();
+ if (mDisplayPowerControllerCallbacks != null) {
+ mDisplayPowerControllerCallbacks.updateWhiteBalance();
}
}
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
index a72b1ed..07821b0 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
@@ -87,15 +87,22 @@
.config_displayWhiteBalanceHighLightAmbientColorTemperature);
final float[] ambientColorTemperatures = getFloatArray(resources,
com.android.internal.R.array.config_displayWhiteBalanceAmbientColorTemperatures);
- final float[] displayColorTempeartures = getFloatArray(resources,
+ final float[] displayColorTemperatures = getFloatArray(resources,
com.android.internal.R.array.config_displayWhiteBalanceDisplayColorTemperatures);
+ final float[] strongAmbientColorTemperatures = getFloatArray(resources,
+ com.android.internal.R.array
+ .config_displayWhiteBalanceStrongAmbientColorTemperatures);
+ final float[] strongDisplayColorTemperatures = getFloatArray(resources,
+ com.android.internal.R.array
+ .config_displayWhiteBalanceStrongDisplayColorTemperatures);
final DisplayWhiteBalanceController controller = new DisplayWhiteBalanceController(
brightnessSensor, brightnessFilter, colorTemperatureSensor, colorTemperatureFilter,
throttler, displayWhiteBalanceLowLightAmbientBrightnesses,
displayWhiteBalanceLowLightAmbientBiases, lowLightAmbientColorTemperature,
displayWhiteBalanceHighLightAmbientBrightnesses,
displayWhiteBalanceHighLightAmbientBiases, highLightAmbientColorTemperature,
- ambientColorTemperatures, displayColorTempeartures);
+ ambientColorTemperatures, displayColorTemperatures, strongAmbientColorTemperatures,
+ strongDisplayColorTemperatures);
brightnessSensor.setCallbacks(controller);
colorTemperatureSensor.setCallbacks(controller);
return controller;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 611b288..f0a6af3 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -416,13 +416,14 @@
mCurrentDreamCanDoze = canDoze;
mCurrentDreamUserId = userId;
+ if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
+ mUiEventLogger.log(DreamManagerEvent.DREAM_START);
+ }
+
PowerManager.WakeLock wakeLock = mPowerManager
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream");
mHandler.post(wakeLock.wrap(() -> {
mAtmInternal.notifyDreamStateChanged(true);
- if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
- mUiEventLogger.log(DreamManagerEvent.DREAM_START);
- }
mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock,
mDreamOverlayServiceName);
}));
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index 65ec1c0..79820a2 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.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 @@
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 @@
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 @@
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,
@@ -385,6 +393,16 @@
R.bool.config_cecTvSendStandbyOnSleepDisabled_allowed,
R.bool.config_cecTvSendStandbyOnSleepDisabled_default);
+ Setting setMenuLanguage = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE,
+ R.bool.config_cecSetMenuLanguage_userConfigurable);
+ setMenuLanguage.registerValue(HdmiControlManager.SET_MENU_LANGUAGE_ENABLED,
+ R.bool.config_cecSetMenuLanguageEnabled_allowed,
+ R.bool.config_cecSetMenuLanguageEnabled_default);
+ setMenuLanguage.registerValue(HdmiControlManager.SET_MENU_LANGUAGE_DISABLED,
+ R.bool.config_cecSetMenuLanguageDisabled_allowed,
+ R.bool.config_cecSetMenuLanguageDisabled_default);
+
Setting rcProfileTv = registerSetting(
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
R.bool.config_cecRcProfileTv_userConfigurable);
@@ -697,6 +715,8 @@
return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE:
+ return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
@@ -768,6 +788,8 @@
return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE:
+ return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index c674ffe..454a76a 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -901,17 +901,12 @@
protected int handleVendorCommandWithId(HdmiCecMessage message) {
byte[] params = message.getParams();
int vendorId = HdmiUtils.threeBytesToInt(params);
- if (vendorId == mService.getVendorId()) {
- if (!mService.invokeVendorCommandListenersOnReceived(
- mDeviceType, message.getSource(), message.getDestination(), params, true)) {
- return Constants.ABORT_REFUSED;
- }
- } else if (message.getDestination() != Constants.ADDR_BROADCAST
- && message.getSource() != Constants.ADDR_UNREGISTERED) {
- Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>");
- return Constants.ABORT_UNRECOGNIZED_OPCODE;
- } else {
+ if (message.getDestination() == Constants.ADDR_BROADCAST
+ || message.getSource() == Constants.ADDR_UNREGISTERED) {
Slog.v(TAG, "Wrong broadcast vendor command. Ignoring");
+ } else if (!mService.invokeVendorCommandListenersOnReceived(
+ mDeviceType, message.getSource(), message.getDestination(), params, true)) {
+ return Constants.ABORT_REFUSED;
}
return Constants.HANDLED;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index b23395f..0edcea5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -17,10 +17,15 @@
package com.android.server.hdmi;
import android.annotation.CallSuper;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.os.Binder;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
@@ -45,9 +50,6 @@
public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
private static final String TAG = "HdmiCecLocalDevicePlayback";
- private static final boolean SET_MENU_LANGUAGE =
- HdmiProperties.set_menu_language_enabled().orElse(false);
-
// How long to wait after hotplug out before possibly going to Standby.
@VisibleForTesting
static final long STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS = 30_000;
@@ -383,7 +385,9 @@
@Constants.HandleMessageResult
protected int handleSetMenuLanguage(HdmiCecMessage message) {
assertRunOnServiceThread();
- if (!SET_MENU_LANGUAGE) {
+ if (mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE)
+ == HdmiControlManager.SET_MENU_LANGUAGE_DISABLED) {
return Constants.ABORT_UNRECOGNIZED_OPCODE;
}
@@ -408,7 +412,7 @@
// locale from being chosen. 'eng' in the CEC command, for instance,
// will always be mapped to en-AU among other variants like en-US, en-GB,
// an en-IN, which may not be the expected one.
- LocalePicker.updateLocale(localeInfo.getLocale());
+ startSetMenuLanguageActivity(localeInfo.getLocale());
return Constants.HANDLED;
}
}
@@ -420,6 +424,24 @@
}
}
+ private void startSetMenuLanguageActivity(Locale locale) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Context context = mService.getContext();
+ Intent intent = new Intent();
+ intent.putExtra(HdmiControlManager.EXTRA_LOCALE, locale.toLanguageTag());
+ intent.setComponent(
+ ComponentName.unflattenFromString(context.getResources().getString(
+ com.android.internal.R.string.config_hdmiCecSetMenuLanguageActivity)));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivityAsUser(intent, context.getUser());
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "unable to start HdmiCecSetMenuLanguageActivity");
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@Override
@Constants.HandleMessageResult
protected int handleSetSystemAudioMode(HdmiCecMessage message) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
index 6f7473d..57fe9e6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
@@ -133,6 +133,7 @@
addHandler(Constants.MESSAGE_SET_OSD_NAME, mBypasser);
addHandler(Constants.MESSAGE_DEVICE_VENDOR_ID, mBypasser);
addHandler(Constants.MESSAGE_REPORT_POWER_STATUS, mBypasser);
+ addHandler(Constants.MESSAGE_GIVE_FEATURES, mBypasser);
addHandler(Constants.MESSAGE_USER_CONTROL_PRESSED, mUserControlProcessedHandler);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 8391e0b..8ac2331 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1701,11 +1701,11 @@
class VendorCommandListenerRecord implements IBinder.DeathRecipient {
private final IHdmiVendorCommandListener mListener;
- private final int mDeviceType;
+ private final int mVendorId;
- public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
+ VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int vendorId) {
mListener = listener;
- mDeviceType = deviceType;
+ mVendorId = vendorId;
}
@Override
@@ -2191,10 +2191,10 @@
}
@Override
- public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
- final int deviceType) {
+ public void addVendorCommandListener(
+ final IHdmiVendorCommandListener listener, final int vendorId) {
initBinderCall();
- HdmiControlService.this.addVendorCommandListener(listener, deviceType);
+ HdmiControlService.this.addVendorCommandListener(listener, vendorId);
}
@Override
@@ -3354,8 +3354,9 @@
mStandbyMessageReceived = false;
}
- private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
- VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
+ @VisibleForTesting
+ void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) {
+ VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, vendorId);
try {
listener.asBinder().linkToDeath(record, 0);
} catch (RemoteException e) {
@@ -3374,8 +3375,14 @@
return false;
}
for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
- if (record.mDeviceType != deviceType) {
- continue;
+ if (hasVendorId) {
+ int vendorId =
+ ((params[0] & 0xFF) << 16)
+ + ((params[1] & 0xFF) << 8)
+ + (params[2] & 0xFF);
+ if (record.mVendorId != vendorId) {
+ continue;
+ }
}
try {
record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
new file mode 100644
index 0000000..dbd3f35
--- /dev/null
+++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
@@ -0,0 +1,95 @@
+/*
+ * 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.input;
+
+import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+
+import android.os.IBinder;
+import android.view.InputApplicationHandle;
+import android.view.InputChannel;
+import android.view.InputMonitor;
+import android.view.InputWindowHandle;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+
+/**
+ * An internal implementation of an {@link InputMonitor} that uses a spy window.
+ *
+ * This spy window is a layer container in the SurfaceFlinger hierarchy that does not have any
+ * graphical buffer, but can still receive input. It is parented to the DisplayContent so
+ * that it can spy on any pointer events that start in the DisplayContent bounds. When the
+ * object is constructed, it will add itself to SurfaceFlinger.
+ */
+class GestureMonitorSpyWindow {
+ final InputApplicationHandle mApplicationHandle;
+ final InputWindowHandle mWindowHandle;
+
+ // The token, InputChannel, and SurfaceControl are owned by this object.
+ final IBinder mMonitorToken;
+ final InputChannel mClientChannel;
+ final SurfaceControl mInputSurface;
+
+ GestureMonitorSpyWindow(IBinder token, String name, int displayId, int pid, int uid,
+ SurfaceControl sc, InputChannel inputChannel) {
+ mMonitorToken = token;
+ mClientChannel = inputChannel;
+ mInputSurface = sc;
+
+ mApplicationHandle = new InputApplicationHandle(null, name,
+ DEFAULT_DISPATCHING_TIMEOUT_MILLIS);
+ mWindowHandle = new InputWindowHandle(mApplicationHandle, displayId);
+
+ mWindowHandle.name = name;
+ mWindowHandle.token = mClientChannel.getToken();
+ mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+ mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ mWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+ mWindowHandle.visible = true;
+ mWindowHandle.focusable = false;
+ mWindowHandle.hasWallpaper = false;
+ mWindowHandle.paused = false;
+ mWindowHandle.ownerPid = pid;
+ mWindowHandle.ownerUid = uid;
+ mWindowHandle.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ mWindowHandle.scaleFactor = 1.0f;
+ mWindowHandle.trustedOverlay = true;
+ 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.setPosition(mInputSurface, 0, 0);
+ t.setCrop(mInputSurface, null /* crop to parent surface */);
+ t.show(mInputSurface);
+
+ t.apply();
+ }
+
+ void remove() {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.hide(mInputSurface);
+ t.remove(mInputSurface);
+ t.apply();
+
+ mClientChannel.dispose();
+ }
+
+ String dump() {
+ return "name='" + mWindowHandle.name + "', inputChannelToken="
+ + mClientChannel.getToken() + " displayId=" + mWindowHandle.displayId;
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index de933cc..bfaa7b3 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -150,6 +150,8 @@
static final String TAG = "InputManager";
static final boolean DEBUG = 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";
@@ -277,6 +279,12 @@
@GuardedBy("mPointerDisplayIdLock")
private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY;
+
+ // Holds all the registered gesture monitors that are implemented as spy windows. The spy
+ // windows are mapped by their InputChannel tokens.
+ @GuardedBy("mInputMonitors")
+ final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
+
private static native long nativeInit(InputManagerService service,
Context context, MessageQueue messageQueue);
private static native void nativeStart(long ptr);
@@ -716,33 +724,77 @@
inputChannelName, Binder.getCallingPid());
}
+ @NonNull
+ private InputChannel createSpyWindowGestureMonitor(IBinder monitorToken, String name,
+ int displayId, int pid, int uid) {
+ final SurfaceControl sc = mWindowManagerCallbacks.createSurfaceForGestureMonitor(name,
+ displayId);
+ if (sc == null) {
+ throw new IllegalArgumentException(
+ "Could not create gesture monitor surface on display: " + displayId);
+ }
+ final InputChannel channel = createInputChannel(name);
+
+ try {
+ monitorToken.linkToDeath(() -> removeSpyWindowGestureMonitor(channel.getToken()), 0);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Client died before '" + name + "' could be created.");
+ return null;
+ }
+ synchronized (mInputMonitors) {
+ mInputMonitors.put(channel.getToken(),
+ new GestureMonitorSpyWindow(monitorToken, name, displayId, pid, uid, sc,
+ channel));
+ }
+
+ final InputChannel outInputChannel = new InputChannel();
+ channel.copyTo(outInputChannel);
+ return outInputChannel;
+ }
+
+ private void removeSpyWindowGestureMonitor(IBinder inputChannelToken) {
+ final GestureMonitorSpyWindow monitor;
+ synchronized (mInputMonitors) {
+ monitor = mInputMonitors.remove(inputChannelToken);
+ }
+ removeInputChannel(inputChannelToken);
+ if (monitor == null) return;
+ monitor.remove();
+ }
+
/**
* Creates an input monitor that will receive pointer events for the purposes of system-wide
* gesture interpretation.
*
- * @param inputChannelName The input channel name.
+ * @param requestedName The input channel name.
* @param displayId Target display id.
* @return The input channel.
*/
@Override // Binder call
- public InputMonitor monitorGestureInput(String inputChannelName, int displayId) {
+ public InputMonitor monitorGestureInput(IBinder monitorToken, @NonNull String requestedName,
+ int displayId) {
if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT,
- "monitorInputRegion()")) {
+ "monitorGestureInput()")) {
throw new SecurityException("Requires MONITOR_INPUT permission");
}
- Objects.requireNonNull(inputChannelName, "inputChannelName must not be null.");
+ Objects.requireNonNull(requestedName, "name must not be null.");
+ Objects.requireNonNull(monitorToken, "token must not be null.");
if (displayId < Display.DEFAULT_DISPLAY) {
throw new IllegalArgumentException("displayId must >= 0.");
}
+ final String name = "[Gesture Monitor] " + requestedName;
final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- InputChannel inputChannel = nativeCreateInputMonitor(
- mPtr, displayId, true /*isGestureMonitor*/, inputChannelName, pid);
- InputMonitorHost host = new InputMonitorHost(inputChannel.getToken());
- return new InputMonitor(inputChannel, host);
+ final InputChannel inputChannel =
+ USE_SPY_WINDOW_GESTURE_MONITORS
+ ? createSpyWindowGestureMonitor(monitorToken, name, displayId, pid, uid)
+ : nativeCreateInputMonitor(mPtr, displayId, true /*isGestureMonitor*/,
+ requestedName, pid);
+ return new InputMonitor(inputChannel, new InputMonitorHost(inputChannel.getToken()));
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2285,14 +2337,8 @@
nativeNotifyPortAssociationsChanged(mPtr);
}
- /**
- * Add a runtime association between the input device name and the display unique id.
- * @param inputDeviceName The name of the input device.
- * @param displayUniqueId The unique id of the associated display.
- */
@Override // Binder call
- public void addUniqueIdAssociation(@NonNull String inputDeviceName,
- @NonNull String displayUniqueId) {
+ public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
if (!checkCallingPermission(
android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
"addNameAssociation()")) {
@@ -2300,20 +2346,16 @@
"Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
}
- Objects.requireNonNull(inputDeviceName);
+ Objects.requireNonNull(inputPort);
Objects.requireNonNull(displayUniqueId);
synchronized (mAssociationsLock) {
- mUniqueIdAssociations.put(inputDeviceName, displayUniqueId);
+ mUniqueIdAssociations.put(inputPort, displayUniqueId);
}
nativeChangeUniqueIdAssociation(mPtr);
}
- /**
- * Remove the runtime association between the input device and the display.
- * @param inputDeviceName The port of the input device to be cleared.
- */
@Override // Binder call
- public void removeUniqueIdAssociation(@NonNull String inputDeviceName) {
+ public void removeUniqueIdAssociation(@NonNull String inputPort) {
if (!checkCallingPermission(
android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
"removeUniqueIdAssociation()")) {
@@ -2321,9 +2363,9 @@
"Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
}
- Objects.requireNonNull(inputDeviceName);
+ Objects.requireNonNull(inputPort);
synchronized (mAssociationsLock) {
- mUniqueIdAssociations.remove(inputDeviceName);
+ mUniqueIdAssociations.remove(inputPort);
}
nativeChangeUniqueIdAssociation(mPtr);
}
@@ -2573,27 +2615,48 @@
String dumpStr = nativeDump(mPtr);
if (dumpStr != null) {
pw.println(dumpStr);
- dumpAssociations(pw);
}
+
+ pw.println("Input Manager Service (Java) State:");
+ dumpAssociations(pw, " " /*prefix*/);
+ dumpSpyWindowGestureMonitors(pw, " " /*prefix*/);
}
- private void dumpAssociations(PrintWriter pw) {
+ private void dumpAssociations(PrintWriter pw, String prefix) {
if (!mStaticAssociations.isEmpty()) {
- pw.println("Static Associations:");
+ pw.println(prefix + "Static Associations:");
mStaticAssociations.forEach((k, v) -> {
- pw.print(" port: " + k);
+ pw.print(prefix + " port: " + k);
pw.println(" display: " + v);
});
}
synchronized (mAssociationsLock) {
if (!mRuntimeAssociations.isEmpty()) {
- pw.println("Runtime Associations:");
+ pw.println(prefix + "Runtime Associations:");
mRuntimeAssociations.forEach((k, v) -> {
- pw.print(" port: " + k);
+ pw.print(prefix + " port: " + k);
pw.println(" display: " + v);
});
}
+ if (!mUniqueIdAssociations.isEmpty()) {
+ pw.println(prefix + "Unique Id Associations:");
+ mUniqueIdAssociations.forEach((k, v) -> {
+ pw.print(prefix + " port: " + k);
+ pw.println(" uniqueId: " + v);
+ });
+ }
+ }
+ }
+
+ private void dumpSpyWindowGestureMonitors(PrintWriter pw, String prefix) {
+ synchronized (mInputMonitors) {
+ if (mInputMonitors.isEmpty()) return;
+ pw.println(prefix + "Gesture Monitors (implemented as spy windows):");
+ int i = 0;
+ for (final GestureMonitorSpyWindow monitor : mInputMonitors.values()) {
+ pw.append(prefix + " " + i++ + ": ").println(monitor.dump());
+ }
}
}
@@ -2621,6 +2684,7 @@
synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */}
synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
synchronized (mPointerDisplayIdLock) { /* Test if blocked by pointer display id lock */ }
+ synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
nativeMonitor(mPtr);
}
@@ -2689,6 +2753,11 @@
// Native callback.
private void notifyInputChannelBroken(IBinder token) {
+ synchronized (mInputMonitors) {
+ if (mInputMonitors.containsKey(token)) {
+ removeSpyWindowGestureMonitor(token);
+ }
+ }
mWindowManagerCallbacks.notifyInputChannelBroken(token);
}
@@ -2724,6 +2793,17 @@
// Native callback
private void notifyWindowUnresponsive(IBinder token, String reason) {
+ int gestureMonitorPid = -1;
+ synchronized (mInputMonitors) {
+ final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token);
+ if (gestureMonitor != null) {
+ gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid;
+ }
+ }
+ if (gestureMonitorPid != -1) {
+ mWindowManagerCallbacks.notifyGestureMonitorUnresponsive(gestureMonitorPid, reason);
+ return;
+ }
mWindowManagerCallbacks.notifyWindowUnresponsive(token, reason);
}
@@ -2734,6 +2814,17 @@
// Native callback
private void notifyWindowResponsive(IBinder token) {
+ int gestureMonitorPid = -1;
+ synchronized (mInputMonitors) {
+ final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token);
+ if (gestureMonitor != null) {
+ gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid;
+ }
+ }
+ if (gestureMonitorPid != -1) {
+ mWindowManagerCallbacks.notifyGestureMonitorResponsive(gestureMonitorPid);
+ return;
+ }
mWindowManagerCallbacks.notifyWindowResponsive(token);
}
@@ -3187,6 +3278,16 @@
* pointers such as the mouse cursor and touch spots for the given display.
*/
SurfaceControl getParentSurfaceForPointers(int displayId);
+
+ /**
+ * Create a {@link SurfaceControl} that can be configured to receive input over the entire
+ * display to implement a gesture monitor. The surface will not have a graphical buffer.
+ * @param name the name of the gesture monitor
+ * @param displayId the display to create the window in
+ * @return the SurfaceControl of the new layer container surface
+ */
+ @Nullable
+ SurfaceControl createSurfaceForGestureMonitor(String name, int displayId);
}
/**
@@ -3261,20 +3362,24 @@
* Interface for the system to handle request from InputMonitors.
*/
private final class InputMonitorHost extends IInputMonitorHost.Stub {
- private final IBinder mToken;
+ private final IBinder mInputChannelToken;
- InputMonitorHost(IBinder token) {
- mToken = token;
+ InputMonitorHost(IBinder inputChannelToken) {
+ mInputChannelToken = inputChannelToken;
}
@Override
public void pilferPointers() {
- nativePilferPointers(mPtr, mToken);
+ nativePilferPointers(mPtr, mInputChannelToken);
}
@Override
public void dispose() {
- nativeRemoveInputChannel(mPtr, mToken);
+ if (USE_SPY_WINDOW_GESTURE_MONITORS) {
+ removeSpyWindowGestureMonitor(mInputChannelToken);
+ return;
+ }
+ nativeRemoveInputChannel(mPtr, mInputChannelToken);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index 4c26166..9846a2b 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 @@
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 @@
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 @@
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 @@
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/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index c87ca92..6cb3b3b 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -107,9 +107,11 @@
@AnyThread
void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
- int configChanges, boolean stylusHwSupported) {
+ int configChanges, boolean stylusHwSupported,
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
try {
- mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported);
+ mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported,
+ shouldShowImeSwitcherWhenImeIsShown);
} catch (RemoteException e) {
logRemoteException(e);
}
@@ -145,9 +147,20 @@
@AnyThread
void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute,
- boolean restarting) {
+ boolean restarting, boolean shouldShowImeSwitcherWhenImeIsShown) {
try {
- mTarget.startInput(startInputToken, inputContext, attribute, restarting);
+ mTarget.startInput(startInputToken, inputContext, attribute, restarting,
+ shouldShowImeSwitcherWhenImeIsShown);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ }
+ }
+
+ @AnyThread
+ void onShouldShowImeSwitcherWhenImeIsShownChanged(boolean shouldShowImeSwitcherWhenImeIsShown) {
+ try {
+ mTarget.onShouldShowImeSwitcherWhenImeIsShownChanged(
+ shouldShowImeSwitcherWhenImeIsShown);
} catch (RemoteException e) {
logRemoteException(e);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 936b1a2..c207738a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2325,10 +2325,12 @@
true /* direct */);
}
+ final boolean shouldShowImeSwitcherWhenImeIsShown =
+ shouldShowImeSwitcherWhenImeIsShownLocked();
final SessionState session = mCurClient.curSession;
setEnabledSessionLocked(session);
- session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting);
-
+ session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting,
+ shouldShowImeSwitcherWhenImeIsShown);
if (mShowRequested) {
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null,
@@ -2528,7 +2530,7 @@
+ mCurTokenDisplayId);
}
inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
- configChanges, supportStylusHw);
+ configChanges, supportStylusHw, shouldShowImeSwitcherWhenImeIsShownLocked());
}
@AnyThread
@@ -2731,6 +2733,12 @@
}
@GuardedBy("ImfLock.class")
+ boolean shouldShowImeSwitcherWhenImeIsShownLocked() {
+ return shouldShowImeSwitcherLocked(
+ InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE);
+ }
+
+ @GuardedBy("ImfLock.class")
private boolean shouldShowImeSwitcherLocked(int visibility) {
if (!mShowOngoingImeSwitcherForPhones) return false;
if (mMenuController.getSwitchingDialogLocked() != null) return false;
@@ -2990,6 +2998,7 @@
// the same enabled IMEs list.
mSwitchingController.resetCircularListLocked(mContext);
+ sendShouldShowImeSwitcherWhenImeIsShownLocked();
}
@GuardedBy("ImfLock.class")
@@ -4308,6 +4317,7 @@
updateImeWindowStatus(msg.arg1 == 1);
return true;
}
+
// ---------------------------------------------------------
case MSG_UNBIND_CLIENT:
@@ -4368,6 +4378,9 @@
// --------------------------------------------------------------
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
+ synchronized (ImfLock.class) {
+ sendShouldShowImeSwitcherWhenImeIsShownLocked();
+ }
return true;
case MSG_SYSTEM_UNLOCK_USER: {
final int userId = msg.arg1;
@@ -4638,6 +4651,8 @@
// the same enabled IMEs list.
mSwitchingController.resetCircularListLocked(mContext);
+ sendShouldShowImeSwitcherWhenImeIsShownLocked();
+
// Notify InputMethodListListeners of the new installed InputMethods.
final List<InputMethodInfo> inputMethodList = new ArrayList<>(mMethodList);
mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
@@ -4645,6 +4660,17 @@
}
@GuardedBy("ImfLock.class")
+ void sendShouldShowImeSwitcherWhenImeIsShownLocked() {
+ final IInputMethodInvoker curMethod = mBindingController.getCurMethod();
+ if (curMethod == null) {
+ // No need to send the data if the IME is not yet bound.
+ return;
+ }
+ curMethod.onShouldShowImeSwitcherWhenImeIsShownChanged(
+ shouldShowImeSwitcherWhenImeIsShownLocked());
+ }
+
+ @GuardedBy("ImfLock.class")
private void updateDefaultVoiceImeIfNeededLocked() {
final String systemSpeechRecognizer =
mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 348bb2d..98bde11 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -203,6 +203,7 @@
attrs.setTitle("Select input method");
w.setAttributes(attrs);
mService.updateSystemUiLocked();
+ mService.sendShouldShowImeSwitcherWhenImeIsShownLocked();
mSwitchingDialog.show();
}
}
@@ -238,6 +239,7 @@
mSwitchingDialogTitleView = null;
mService.updateSystemUiLocked();
+ mService.sendShouldShowImeSwitcherWhenImeIsShownLocked();
mDialogBuilder = null;
mIms = null;
}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 134fb96..01aee7b 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -31,13 +31,11 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
-import android.os.BestClock;
import android.os.Binder;
import android.os.HandlerThread;
import android.os.LocaleList;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;
@@ -61,7 +59,6 @@
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Duration;
-import java.time.ZoneOffset;
import java.util.HashMap;
/**
@@ -97,15 +94,10 @@
LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
PackageManagerInternal pmInternal) {
- this(localeManagerService.mContext, localeManagerService, pmInternal, getDefaultClock(),
+ this(localeManagerService.mContext, localeManagerService, pmInternal, Clock.systemUTC(),
new SparseArray<>());
}
- private static @NonNull Clock getDefaultClock() {
- return new BestClock(ZoneOffset.UTC, SystemClock.currentNetworkTimeClock(),
- Clock.systemUTC());
- }
-
@VisibleForTesting LocaleManagerBackupHelper(Context context,
LocaleManagerService localeManagerService,
PackageManagerInternal pmInternal, Clock clock, SparseArray<StagedData> stagedData) {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index aa1fa9b..0c3f9f0 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.location;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.app.compat.CompatChanges.isChangeEnabled;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
@@ -829,16 +830,12 @@
"only verified adas packages may use adas gnss bypass requests");
}
if (!isLocationProvider) {
- mContext.enforceCallingOrSelfPermission(
- permission.WRITE_SECURE_SETTINGS,
- "adas gnss bypass requires " + permission.WRITE_SECURE_SETTINGS);
+ LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
}
}
if (request.isLocationSettingsIgnored()) {
if (!isLocationProvider) {
- mContext.enforceCallingOrSelfPermission(
- permission.WRITE_SECURE_SETTINGS,
- "ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS);
+ LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
}
}
@@ -933,16 +930,12 @@
"only verified adas packages may use adas gnss bypass requests");
}
if (!isLocationProvider) {
- mContext.enforceCallingOrSelfPermission(
- permission.WRITE_SECURE_SETTINGS,
- "adas gnss bypass requires " + permission.WRITE_SECURE_SETTINGS);
+ LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
}
}
if (request.isLocationSettingsIgnored()) {
if (!isLocationProvider) {
- mContext.enforceCallingOrSelfPermission(
- permission.WRITE_SECURE_SETTINGS,
- "ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS);
+ LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
}
}
@@ -1202,7 +1195,7 @@
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, false, "setLocationEnabledForUser", null);
- mContext.enforceCallingOrSelfPermission(permission.WRITE_SECURE_SETTINGS, null);
+ mContext.enforceCallingOrSelfPermission(WRITE_SECURE_SETTINGS, null);
LocationManager.invalidateLocalLocationEnabledCaches();
mInjector.getSettingsHelper().setLocationEnabled(enabled, userId);
@@ -1220,7 +1213,7 @@
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, false, "setAdasGnssLocationEnabledForUser", null);
- mContext.enforceCallingOrSelfPermission(permission.WRITE_SECURE_SETTINGS, null);
+ LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
mInjector.getLocationSettings().updateUserSettings(userId,
settings -> settings.withAdasGnssLocationEnabled(enabled));
diff --git a/services/core/java/com/android/server/location/LocationPermissions.java b/services/core/java/com/android/server/location/LocationPermissions.java
index 7528f8b..be702d9 100644
--- a/services/core/java/com/android/server/location/LocationPermissions.java
+++ b/services/core/java/com/android/server/location/LocationPermissions.java
@@ -18,6 +18,8 @@
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.LOCATION_BYPASS;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.annotation.IntDef;
@@ -121,6 +123,29 @@
}
/**
+ * Throws a security exception if the caller does not hold the required bypass permissions.
+ */
+ public static void enforceCallingOrSelfBypassPermission(Context context) {
+ enforceBypassPermission(context, Binder.getCallingUid(), Binder.getCallingPid());
+ }
+
+ /**
+ * Throws a security exception if the given uid/pid does not hold the required bypass
+ * perissions.
+ */
+ public static void enforceBypassPermission(Context context, int uid, int pid) {
+ if (context.checkPermission(WRITE_SECURE_SETTINGS, pid, uid) == PERMISSION_GRANTED) {
+ // TODO: disallow WRITE_SECURE_SETTINGS permission.
+ return;
+ }
+ if (context.checkPermission(LOCATION_BYPASS, pid, uid) == PERMISSION_GRANTED) {
+ return;
+ }
+ throw new SecurityException("uid" + uid + " does not have " + LOCATION_BYPASS
+ + "or " + WRITE_SECURE_SETTINGS + ".");
+ }
+
+ /**
* Returns false if the caller does not hold the required location permissions.
*/
public static boolean checkCallingOrSelfLocationPermission(Context context,
diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java
index b65338d..9c85d18 100644
--- a/services/core/java/com/android/server/location/LocationShellCommand.java
+++ b/services/core/java/com/android/server/location/LocationShellCommand.java
@@ -69,6 +69,14 @@
handleSetAdasGnssLocationEnabled();
return 0;
}
+ case "set-automotive-gnss-suspended": {
+ handleSetAutomotiveGnssSuspended();
+ return 0;
+ }
+ case "is-automotive-gnss-suspended": {
+ handleIsAutomotiveGnssSuspended();
+ return 0;
+ }
case "providers": {
String command = getNextArgRequired();
return parseProvidersCommand(command);
@@ -189,6 +197,24 @@
mService.setAdasGnssLocationEnabledForUser(enabled, userId);
}
+ private void handleSetAutomotiveGnssSuspended() {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ throw new IllegalStateException("command only recognized on automotive devices");
+ }
+
+ boolean suspended = Boolean.parseBoolean(getNextArgRequired());
+
+ mService.setAutomotiveGnssSuspended(suspended);
+ }
+
+ private void handleIsAutomotiveGnssSuspended() {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ throw new IllegalStateException("command only recognized on automotive devices");
+ }
+
+ getOutPrintWriter().println(mService.isAutomotiveGnssSuspended());
+ }
+
private void handleAddTestProvider() {
String provider = getNextArgRequired();
@@ -359,6 +385,10 @@
pw.println(" set-adas-gnss-location-enabled true|false [--user <USER_ID>]");
pw.println(" Sets the ADAS GNSS location enabled state. If no user is specified,");
pw.println(" the current user is assumed.");
+ pw.println(" is-automotive-gnss-suspended");
+ pw.println(" Gets the automotive GNSS suspended state.");
+ pw.println(" set-automotive-gnss-suspended true|false");
+ pw.println(" Sets the automotive GNSS suspended state.");
}
pw.println(" providers");
pw.println(" The providers command is followed by a subcommand, as listed below:");
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 21ea1f6..a1ee46b 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -465,7 +465,7 @@
try {
mHub.onHostEndpointConnected(info);
} catch (RemoteException | ServiceSpecificException e) {
- Log.e(TAG, "RemoteException in onHostEndpointConnected");
+ Log.e(TAG, "Exception in onHostEndpointConnected" + e.getMessage());
}
}
@@ -474,7 +474,7 @@
try {
mHub.onHostEndpointDisconnected((char) hostEndpointId);
} catch (RemoteException | ServiceSpecificException e) {
- Log.e(TAG, "RemoteException in onHostEndpointDisconnected");
+ Log.e(TAG, "Exception in onHostEndpointDisconnected" + e.getMessage());
}
}
@@ -488,6 +488,8 @@
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
}
@@ -499,8 +501,10 @@
try {
mHub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
}
@@ -510,8 +514,10 @@
try {
mHub.unloadNanoapp(contextHubId, nanoappId, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
}
@@ -521,8 +527,10 @@
try {
mHub.enableNanoapp(contextHubId, nanoappId, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
}
@@ -532,8 +540,10 @@
try {
mHub.disableNanoapp(contextHubId, nanoappId, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
}
@@ -542,8 +552,10 @@
try {
mHub.queryNanoapps(contextHubId);
return ContextHubTransaction.RESULT_SUCCESS;
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
}
@@ -551,7 +563,7 @@
mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
try {
mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId));
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException | IllegalArgumentException e) {
Log.e(TAG, "Exception while registering callback: " + e.getMessage());
}
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
index 5036a6e..bfef978 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
@@ -63,16 +63,25 @@
@Override
protected boolean registerWithService(Void ignored,
Collection<GnssListenerRegistration> registrations) {
- if (D) {
- Log.d(TAG, "starting gnss nmea messages");
+ if (mGnssNative.startNmeaMessageCollection()) {
+ if (D) {
+ Log.d(TAG, "starting gnss nmea messages collection");
+ }
+ return true;
+ } else {
+ Log.e(TAG, "error starting gnss nmea messages collection");
+ return false;
}
- return true;
}
@Override
protected void unregisterWithService() {
- if (D) {
- Log.d(TAG, "stopping gnss nmea messages");
+ if (mGnssNative.stopNmeaMessageCollection()) {
+ if (D) {
+ Log.d(TAG, "stopping gnss nmea messages collection");
+ }
+ } else {
+ Log.e(TAG, "error stopping gnss nmea messages collection");
}
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
index 936283d..0ce36d6 100644
--- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
@@ -43,6 +43,7 @@
private final AppOpsHelper mAppOpsHelper;
private final LocationUsageLogger mLogger;
+ private final GnssNative mGnssNative;
private boolean mIsNavigating = false;
@@ -50,6 +51,7 @@
super(injector);
mAppOpsHelper = injector.getAppOpsHelper();
mLogger = injector.getLocationUsageLogger();
+ mGnssNative = gnssNative;
gnssNative.addBaseCallbacks(this);
gnssNative.addStatusCallbacks(this);
@@ -64,16 +66,25 @@
@Override
protected boolean registerWithService(Void ignored,
Collection<GnssListenerRegistration> registrations) {
- if (D) {
- Log.d(TAG, "starting gnss status");
+ if (mGnssNative.startSvStatusCollection()) {
+ if (D) {
+ Log.d(TAG, "starting gnss sv status");
+ }
+ return true;
+ } else {
+ Log.e(TAG, "error starting gnss sv status");
+ return false;
}
- return true;
}
@Override
protected void unregisterWithService() {
- if (D) {
- Log.d(TAG, "stopping gnss status");
+ if (mGnssNative.stopSvStatusCollection()) {
+ if (D) {
+ Log.d(TAG, "stopping gnss sv status");
+ }
+ } else {
+ Log.e(TAG, "error stopping gnss sv status");
}
}
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index e072bf7..af87677 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -783,6 +783,38 @@
}
/**
+ * Starts sv status collection.
+ */
+ public boolean startSvStatusCollection() {
+ Preconditions.checkState(mRegistered);
+ return mGnssHal.startSvStatusCollection();
+ }
+
+ /**
+ * Stops sv status collection.
+ */
+ public boolean stopSvStatusCollection() {
+ Preconditions.checkState(mRegistered);
+ return mGnssHal.stopSvStatusCollection();
+ }
+
+ /**
+ * Starts NMEA message collection.
+ */
+ public boolean startNmeaMessageCollection() {
+ Preconditions.checkState(mRegistered);
+ return mGnssHal.startNmeaMessageCollection();
+ }
+
+ /**
+ * Stops NMEA message collection.
+ */
+ public boolean stopNmeaMessageCollection() {
+ Preconditions.checkState(mRegistered);
+ return mGnssHal.stopNmeaMessageCollection();
+ }
+
+ /**
* Returns true if measurement corrections are supported.
*/
public boolean isMeasurementCorrectionsSupported() {
@@ -1369,6 +1401,22 @@
return native_inject_measurement_corrections(corrections);
}
+ protected boolean startSvStatusCollection() {
+ return native_start_sv_status_collection();
+ }
+
+ protected boolean stopSvStatusCollection() {
+ return native_stop_sv_status_collection();
+ }
+
+ protected boolean startNmeaMessageCollection() {
+ return native_start_nmea_message_collection();
+ }
+
+ protected boolean stopNmeaMessageCollection() {
+ return native_stop_nmea_message_collection();
+ }
+
protected int getBatchSize() {
return native_get_batch_size();
}
@@ -1478,6 +1526,10 @@
private static native int native_read_nmea(byte[] buffer, int bufferSize);
+ private static native boolean native_start_nmea_message_collection();
+
+ private static native boolean native_stop_nmea_message_collection();
+
// location injection APIs
private static native void native_inject_location(
@@ -1501,6 +1553,11 @@
private static native void native_inject_time(long time, long timeReference, int uncertainty);
+ // sv status APIs
+ private static native boolean native_start_sv_status_collection();
+
+ private static native boolean native_stop_sv_status_collection();
+
// navigation message APIs
private static native boolean native_is_navigation_message_supported();
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8f05130..2d2edfa 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.READ_CONTACTS;
import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
+import static android.Manifest.permission.SET_INITIAL_LOCK;
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL;
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE;
@@ -1650,9 +1651,13 @@
"This operation requires secure lock screen feature");
}
if (!hasPermission(PERMISSION) && !hasPermission(SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS)) {
- throw new SecurityException(
- "setLockCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or "
- + PERMISSION);
+ if (hasPermission(SET_INITIAL_LOCK) && savedCredential.isNone()) {
+ // SET_INITIAL_LOCK can only be used if credential is not set.
+ } else {
+ throw new SecurityException(
+ "setLockCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or "
+ + PERMISSION);
+ }
}
final long identity = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 303ab46..7f997df 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -677,9 +677,9 @@
UserRecord userRecord = routerRecord.mUserRecord;
userRecord.mRouterRecords.remove(routerRecord);
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers,
+ obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
routerRecord.mUserRecord.mHandler,
- routerRecord.mPackageName, /* preferredFeatures=*/ null));
+ routerRecord.mPackageName, null));
userRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
userRecord.mHandler));
@@ -694,10 +694,10 @@
}
routerRecord.mDiscoveryPreference = discoveryRequest;
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers,
+ obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
routerRecord.mUserRecord.mHandler,
routerRecord.mPackageName,
- routerRecord.mDiscoveryPreference.getPreferredFeatures()));
+ routerRecord.mDiscoveryPreference));
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
routerRecord.mUserRecord.mHandler));
@@ -921,7 +921,7 @@
// TODO: UserRecord <-> routerRecord, why do they reference each other?
// How about removing mUserRecord from routerRecord?
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManager,
+ obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManager,
routerRecord.mUserRecord.mHandler, routerRecord, manager));
}
@@ -2118,19 +2118,19 @@
}
}
- private void notifyPreferredFeaturesChangedToManager(@NonNull RouterRecord routerRecord,
+ private void notifyDiscoveryPreferenceChangedToManager(@NonNull RouterRecord routerRecord,
@NonNull IMediaRouter2Manager manager) {
try {
- manager.notifyPreferredFeaturesChanged(routerRecord.mPackageName,
- routerRecord.mDiscoveryPreference.getPreferredFeatures());
+ manager.notifyDiscoveryPreferenceChanged(routerRecord.mPackageName,
+ routerRecord.mDiscoveryPreference);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify preferred features changed."
+ " Manager probably died.", ex);
}
}
- private void notifyPreferredFeaturesChangedToManagers(@NonNull String routerPackageName,
- @Nullable List<String> preferredFeatures) {
+ private void notifyDiscoveryPreferenceChangedToManagers(@NonNull String routerPackageName,
+ @Nullable RouteDiscoveryPreference discoveryPreference) {
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
return;
@@ -2143,7 +2143,8 @@
}
for (IMediaRouter2Manager manager : managers) {
try {
- manager.notifyPreferredFeaturesChanged(routerPackageName, preferredFeatures);
+ manager.notifyDiscoveryPreferenceChanged(routerPackageName,
+ discoveryPreference);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify preferred features changed."
+ " Manager probably died.", ex);
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 583cdd5..647a89e 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -104,6 +104,12 @@
return -1 * Boolean.compare(leftPeople, rightPeople);
}
+ boolean leftSystemMax = isSystemMax(left);
+ boolean rightSystemMax = isSystemMax(right);
+ if (leftSystemMax != rightSystemMax) {
+ return -1 * Boolean.compare(leftSystemMax, rightSystemMax);
+ }
+
if (leftImportance != rightImportance) {
// by importance, high to low
return -1 * Integer.compare(leftImportance, rightImportance);
@@ -173,6 +179,20 @@
return mMessagingUtil.isImportantMessaging(record.getSbn(), record.getImportance());
}
+ protected boolean isSystemMax(NotificationRecord record) {
+ if (record.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
+ return false;
+ }
+ String packageName = record.getSbn().getPackageName();
+ if ("android".equals(packageName)) {
+ return true;
+ }
+ if ("com.android.systemui".equals(packageName)) {
+ return true;
+ }
+ return false;
+ }
+
private boolean isOngoing(NotificationRecord record) {
final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE;
return (record.getNotification().flags & ongoingFlags) != 0;
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 96d7521..70e968f 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 @@
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 @@
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/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 0cbdbc1..5d18069 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -19,7 +19,7 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
-import static android.permission.PermissionManager.PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.Manifest;
import android.annotation.NonNull;
@@ -77,7 +77,8 @@
assertFlag();
final long callingId = Binder.clearCallingIdentity();
try {
- return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
+ return mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(uid)
+ == PERMISSION_GRANTED;
} finally {
Binder.restoreCallingIdentity(callingId);
}
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index d7bc3bb..dc4d04f 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 @@
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 @@
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 @@
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 @@
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 @@
} 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 @@
// 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 @@
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 @@
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 @@
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 b186f61..d04b331 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -24,18 +24,24 @@
import android.content.ComponentName;
import android.content.Context;
import android.media.AudioAttributes;
+import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.service.notification.ZenModeConfig;
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;
import com.android.internal.util.NotificationMessagingUtil;
import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
import java.util.Date;
public class ZenModeFiltering {
@@ -64,13 +70,22 @@
pw.print(prefix); pw.print("RepeatCallers.mThresholdMinutes=");
pw.println(REPEAT_CALLERS.mThresholdMinutes);
synchronized (REPEAT_CALLERS) {
- if (!REPEAT_CALLERS.mCalls.isEmpty()) {
- pw.print(prefix); pw.println("RepeatCallers.mCalls=");
- for (int i = 0; i < REPEAT_CALLERS.mCalls.size(); i++) {
+ if (!REPEAT_CALLERS.mTelCalls.isEmpty()) {
+ pw.print(prefix); pw.println("RepeatCallers.mTelCalls=");
+ for (int i = 0; i < REPEAT_CALLERS.mTelCalls.size(); i++) {
pw.print(prefix); pw.print(" ");
- pw.print(REPEAT_CALLERS.mCalls.keyAt(i));
+ pw.print(REPEAT_CALLERS.mTelCalls.keyAt(i));
pw.print(" at ");
- pw.println(ts(REPEAT_CALLERS.mCalls.valueAt(i)));
+ pw.println(ts(REPEAT_CALLERS.mTelCalls.valueAt(i)));
+ }
+ }
+ if (!REPEAT_CALLERS.mOtherCalls.isEmpty()) {
+ pw.print(prefix); pw.println("RepeatCallers.mOtherCalls=");
+ for (int i = 0; i < REPEAT_CALLERS.mOtherCalls.size(); i++) {
+ pw.print(prefix); pw.print(" ");
+ pw.print(REPEAT_CALLERS.mOtherCalls.keyAt(i));
+ pw.print(" at ");
+ pw.println(ts(REPEAT_CALLERS.mOtherCalls.valueAt(i)));
}
}
}
@@ -126,7 +141,7 @@
}
protected void recordCall(NotificationRecord record) {
- REPEAT_CALLERS.recordCall(mContext, extras(record));
+ REPEAT_CALLERS.recordCall(mContext, extras(record), record.getPhoneNumbers());
}
/**
@@ -330,34 +345,40 @@
}
private static class RepeatCallers {
- // Person : time
- private final ArrayMap<String, Long> mCalls = new ArrayMap<>();
+ // We keep a separate map per uri scheme to do more generous number-matching
+ // handling on telephone numbers specifically. For other inputs, we
+ // simply match directly on the string.
+ private final ArrayMap<String, Long> mTelCalls = new ArrayMap<>();
+ 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 peopleString = peopleString(extras);
- if (peopleString == null) return;
+ final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
+ if (extraPeople == null || extraPeople.length == 0) return;
final long now = System.currentTimeMillis();
- cleanUp(mCalls, now);
- mCalls.put(peopleString, now);
+ cleanUp(mTelCalls, now);
+ cleanUp(mOtherCalls, now);
+ recordCallers(extraPeople, phoneNumbers, now);
}
private synchronized boolean isRepeat(Context context, Bundle extras) {
setThresholdMinutes(context);
if (mThresholdMinutes <= 0 || extras == null) return false;
- final String peopleString = peopleString(extras);
- if (peopleString == null) return false;
+ final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
+ if (extraPeople == null || extraPeople.length == 0) return false;
final long now = System.currentTimeMillis();
- cleanUp(mCalls, now);
- return mCalls.containsKey(peopleString);
+ cleanUp(mTelCalls, now);
+ cleanUp(mOtherCalls, now);
+ return checkCallers(context, extraPeople);
}
private synchronized void cleanUp(ArrayMap<String, Long> calls, long now) {
final int N = calls.size();
for (int i = N - 1; i >= 0; i--) {
- final long time = mCalls.valueAt(i);
+ final long time = calls.valueAt(i);
if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) {
calls.removeAt(i);
}
@@ -367,10 +388,16 @@
// Clean up all calls that occurred after the given time.
// Used only for tests, to clean up after testing.
private synchronized void cleanUpCallsAfter(long timeThreshold) {
- for (int i = mCalls.size() - 1; i >= 0; i--) {
- final long time = mCalls.valueAt(i);
+ for (int i = mTelCalls.size() - 1; i >= 0; i--) {
+ final long time = mTelCalls.valueAt(i);
if (time > timeThreshold) {
- mCalls.removeAt(i);
+ mTelCalls.removeAt(i);
+ }
+ }
+ for (int j = mOtherCalls.size() - 1; j >= 0; j--) {
+ final long time = mOtherCalls.valueAt(j);
+ if (time > timeThreshold) {
+ mOtherCalls.removeAt(j);
}
}
}
@@ -382,21 +409,74 @@
}
}
- private static String peopleString(Bundle extras) {
- final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
- if (extraPeople == null || extraPeople.length == 0) return null;
- final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < extraPeople.length; i++) {
- String extraPerson = extraPeople[i];
- if (extraPerson == null) continue;
- extraPerson = extraPerson.trim();
- if (extraPerson.isEmpty()) continue;
- if (sb.length() > 0) {
- sb.append('|');
+ 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;
+ final Uri uri = Uri.parse(person);
+ if ("tel".equals(uri.getScheme())) {
+ String tel = uri.getSchemeSpecificPart();
+ // while ideally we should not need to do this, sometimes we have seen tel
+ // numbers given in a url-encoded format
+ try {
+ tel = URLDecoder.decode(tel, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // ignore, keep the original tel string
+ Slog.w(TAG, "unsupported encoding in tel: uri input");
+ }
+ mTelCalls.put(tel, now);
+ } else {
+ // for non-tel calls, store the entire string, uri-component and all
+ mOtherCalls.put(person, now);
}
- sb.append(extraPerson);
}
- return sb.length() == 0 ? null : sb.toString();
+
+ // 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) {
+ // get the default country code for checking telephone numbers
+ final String defaultCountryCode =
+ context.getSystemService(TelephonyManager.class).getNetworkCountryIso();
+ for (int i = 0; i < people.length; i++) {
+ String person = people[i];
+ if (person == null) continue;
+ final Uri uri = Uri.parse(person);
+ if ("tel".equals(uri.getScheme())) {
+ String number = uri.getSchemeSpecificPart();
+ if (mTelCalls.containsKey(number)) {
+ // check directly via map first
+ return true;
+ } else {
+ // see if a number that matches via areSameNumber exists
+ String numberToCheck = number;
+ try {
+ numberToCheck = URLDecoder.decode(number, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // ignore, continue to use the original string
+ Slog.w(TAG, "unsupported encoding in tel: uri input");
+ }
+ for (String prev : mTelCalls.keySet()) {
+ if (PhoneNumberUtils.areSamePhoneNumber(
+ numberToCheck, prev, defaultCountryCode)) {
+ return true;
+ }
+ }
+ }
+ } else {
+ if (mOtherCalls.containsKey(person)) {
+ return true;
+ }
+ }
+ }
+ return false;
}
}
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 2e9ad50..2d87099 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -32,9 +32,6 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.SigningDetails;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.component.ParsedApexSystemService;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.Binder;
@@ -59,6 +56,9 @@
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.component.ParsedApexSystemService;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.utils.TimingsTraceAndSlog;
import com.google.android.collect.Lists;
@@ -414,9 +414,11 @@
throws PackageManagerException;
/**
- * Get a map of system services defined in an apex mapped to the jar files they reside in.
+ * Get a list of apex system services implemented in an apex.
+ *
+ * <p>The list is sorted by initOrder for consistency.
*/
- public abstract Map<String, String> getApexSystemServices();
+ public abstract List<ApexSystemServiceInfo> getApexSystemServices();
/**
* Dumps various state information to the provided {@link PrintWriter} object.
@@ -449,7 +451,7 @@
* Map of all apex system services to the jar files they are contained in.
*/
@GuardedBy("mLock")
- private Map<String, String> mApexSystemServices = new ArrayMap<>();
+ private List<ApexSystemServiceInfo> mApexSystemServices = new ArrayList<>();
/**
* Contains the list of {@code packageName}s of apks-in-apex for given
@@ -605,14 +607,19 @@
}
String name = service.getName();
- if (mApexSystemServices.containsKey(name)) {
- throw new IllegalStateException(String.format(
- "Duplicate apex-system-service %s from %s, %s",
- name, mApexSystemServices.get(name), service.getJarPath()));
+ for (ApexSystemServiceInfo info : mApexSystemServices) {
+ if (info.getName().equals(name)) {
+ throw new IllegalStateException(String.format(
+ "Duplicate apex-system-service %s from %s, %s",
+ name, info.mJarPath, service.getJarPath()));
+ }
}
- mApexSystemServices.put(name, service.getJarPath());
+ ApexSystemServiceInfo info = new ApexSystemServiceInfo(
+ service.getName(), service.getJarPath(), service.getInitOrder());
+ mApexSystemServices.add(info);
}
+ Collections.sort(mApexSystemServices);
mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
if (ai.isActive) {
if (activePackagesSet.contains(packageInfo.packageName)) {
@@ -1133,7 +1140,7 @@
}
@Override
- public Map<String, String> getApexSystemServices() {
+ public List<ApexSystemServiceInfo> getApexSystemServices() {
synchronized (mLock) {
Preconditions.checkState(mApexSystemServices != null,
"APEX packages have not been scanned");
@@ -1423,10 +1430,10 @@
}
@Override
- public Map<String, String> getApexSystemServices() {
+ public List<ApexSystemServiceInfo> getApexSystemServices() {
// TODO(satayev): we can't really support flattened apex use case, and need to migrate
// the manifest entries into system's manifest asap.
- return Collections.emptyMap();
+ return Collections.emptyList();
}
@Override
diff --git a/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java b/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java
new file mode 100644
index 0000000..f75ba6d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java
@@ -0,0 +1,58 @@
+/*
+ * 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.pm;
+
+import android.annotation.Nullable;
+
+/**
+ * A helper class that contains information about apex-system-service to be used within system
+ * server process.
+ */
+public final class ApexSystemServiceInfo implements Comparable<ApexSystemServiceInfo> {
+
+ final String mName;
+ @Nullable
+ final String mJarPath;
+ final int mInitOrder;
+
+ public ApexSystemServiceInfo(String name, String jarPath, int initOrder) {
+ this.mName = name;
+ this.mJarPath = jarPath;
+ this.mInitOrder = initOrder;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getJarPath() {
+ return mJarPath;
+ }
+
+ public int getInitOrder() {
+ return mInitOrder;
+ }
+
+ @Override
+ public int compareTo(ApexSystemServiceInfo other) {
+ if (mInitOrder == other.mInitOrder) {
+ return mName.compareTo(other.mName);
+ }
+ // higher initOrder values take precedence
+ return -Integer.compare(mInitOrder, other.mInitOrder);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 50c26f4..336da2a 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -141,7 +141,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.security.VerityUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
@@ -2358,26 +2358,26 @@
if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
// Check for updated system application.
if (installedPkg.isSystem()) {
- return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
} else {
// If current upgrade specifies particular preference
if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
// Application explicitly specified internal.
- return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
} else if (
installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
// App explicitly prefers external. Let policy decide
} else {
// Prefer previous location
if (installedPkg.isExternalStorage()) {
- return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
}
- return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
}
}
} else {
// Invalid install. Return error code
- return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;
+ return InstallLocationUtils.RECOMMEND_FAILED_ALREADY_EXISTS;
}
}
}
diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java
index d996fe4..7e845c7 100644
--- a/services/core/java/com/android/server/pm/InstallParams.java
+++ b/services/core/java/com/android/server/pm/InstallParams.java
@@ -35,18 +35,17 @@
import android.content.pm.PackageManager;
import android.content.pm.SigningDetails;
import android.content.pm.parsing.PackageLite;
-import android.os.Environment;
import android.os.Message;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.storage.StorageManager;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.Slog;
import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.File;
import java.util.ArrayList;
@@ -142,12 +141,8 @@
* Only {@link PackageManager#INSTALL_INTERNAL} flag may mutate in
* {@link #mInstallFlags}
*/
- private int overrideInstallLocation(PackageInfoLite pkgLite) {
- final boolean ephemeral = (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
- if (DEBUG_INSTANT && ephemeral) {
- Slog.v(TAG, "pkgLite for install: " + pkgLite);
- }
-
+ private int overrideInstallLocation(String packageName, int recommendedInstallLocation,
+ int installLocation) {
if (mOriginInfo.mStaged) {
// If we're already staged, we've firmly committed to an install location
if (mOriginInfo.mFile != null) {
@@ -155,77 +150,35 @@
} else {
throw new IllegalStateException("Invalid stage location");
}
- } else if (pkgLite.recommendedInstallLocation
- == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
- /*
- * If we are not staged and have too little free space, try to free cache
- * before giving up.
- */
- // TODO: focus freeing disk space on the target device
- final StorageManager storage = StorageManager.from(mPm.mContext);
- final long lowThreshold = storage.getStorageLowBytes(
- Environment.getDataDirectory());
-
- final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(
- mOriginInfo.mResolvedPath, mPackageAbiOverride);
- if (sizeBytes >= 0) {
- synchronized (mPm.mInstallLock) {
- try {
- mPm.mInstaller.freeCache(null, sizeBytes + lowThreshold, 0);
- pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mPm.mContext,
- mPackageLite, mOriginInfo.mResolvedPath, mInstallFlags,
- mPackageAbiOverride);
- } catch (Installer.InstallerException e) {
- Slog.w(TAG, "Failed to free cache", e);
- }
- }
- }
-
- /*
- * The cache free must have deleted the file we downloaded to install.
- *
- * TODO: fix the "freeCache" call to not delete the file we care about.
- */
- if (pkgLite.recommendedInstallLocation
- == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
- pkgLite.recommendedInstallLocation =
- PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+ }
+ if (recommendedInstallLocation < 0) {
+ return InstallLocationUtils.getInstallationErrorCode(recommendedInstallLocation);
+ }
+ // Override with defaults if needed.
+ synchronized (mPm.mLock) {
+ // reader
+ AndroidPackage installedPkg = mPm.mPackages.get(packageName);
+ if (installedPkg != null) {
+ // Currently installed package which the new package is attempting to replace
+ recommendedInstallLocation = InstallLocationUtils.installLocationPolicy(
+ installLocation, recommendedInstallLocation, mInstallFlags,
+ installedPkg.isSystem(), installedPkg.isExternalStorage());
}
}
- int ret = INSTALL_SUCCEEDED;
- int loc = pkgLite.recommendedInstallLocation;
- if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
- ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
- } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
- ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
- } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
- ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
- ret = PackageManager.INSTALL_FAILED_INVALID_APK;
- } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
- ret = PackageManager.INSTALL_FAILED_INVALID_URI;
- } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
- ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
- } else {
- // Override with defaults if needed.
- loc = mInstallPackageHelper.installLocationPolicy(pkgLite, mInstallFlags);
+ final boolean onInt = (mInstallFlags & PackageManager.INSTALL_INTERNAL) != 0;
- final boolean onInt = (mInstallFlags & PackageManager.INSTALL_INTERNAL) != 0;
-
- if (!onInt) {
- // Override install location with flags
- if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
- // Set the flag to install on external media.
- mInstallFlags &= ~PackageManager.INSTALL_INTERNAL;
- } else {
- // Make sure the flag for installing on external
- // media is unset
- mInstallFlags |= PackageManager.INSTALL_INTERNAL;
- }
+ if (!onInt) {
+ // Override install location with flags
+ if (recommendedInstallLocation == InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL) {
+ // Set the flag to install on external media.
+ mInstallFlags &= ~PackageManager.INSTALL_INTERNAL;
+ } else {
+ // Make sure the flag for installing on external media is unset
+ mInstallFlags |= PackageManager.INSTALL_INTERNAL;
}
}
- return ret;
+ return INSTALL_SUCCEEDED;
}
/*
@@ -254,7 +207,21 @@
}
}
- mRet = overrideInstallLocation(pkgLite);
+ final boolean ephemeral = (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+ if (DEBUG_INSTANT && ephemeral) {
+ Slog.v(TAG, "pkgLite for install: " + pkgLite);
+ }
+
+ if (!mOriginInfo.mStaged && pkgLite.recommendedInstallLocation
+ == InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
+ // If we are not staged and have too little free space, try to free cache
+ // before giving up.
+ pkgLite.recommendedInstallLocation = mPm.freeCacheForInstallation(
+ pkgLite.recommendedInstallLocation, mPackageLite,
+ mOriginInfo.mResolvedPath, mPackageAbiOverride, mInstallFlags);
+ }
+ mRet = overrideInstallLocation(pkgLite.packageName, pkgLite.recommendedInstallLocation,
+ pkgLite.installLocation);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ccc375f..8465248 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -84,7 +84,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.ImageUtils;
@@ -782,7 +782,7 @@
// If caller requested explicit location, validity check it, otherwise
// resolve the best internal or adopted location.
if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
- if (!PackageHelper.fitsOnInternal(mContext, params)) {
+ if (!InstallLocationUtils.fitsOnInternal(mContext, params)) {
throw new IOException("No suitable internal storage available");
}
} else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
@@ -796,7 +796,7 @@
// requested install flags, delta size, and manifest settings.
final long ident = Binder.clearCallingIdentity();
try {
- params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
+ params.volumeUuid = InstallLocationUtils.resolveInstallVolume(mContext, params);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 390dd3f..7152783 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -143,8 +143,8 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.content.NativeLibraryHelper;
-import com.android.internal.content.PackageHelper;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.os.SomeArgs;
import com.android.internal.security.VerityUtils;
@@ -1537,7 +1537,7 @@
if (stageDir != null && lengthBytes > 0) {
mContext.getSystemService(StorageManager.class).allocateBytes(
targetPfd.getFileDescriptor(), lengthBytes,
- PackageHelper.translateAllocateFlags(params.installFlags));
+ InstallLocationUtils.translateAllocateFlags(params.installFlags));
}
if (offsetBytes > 0) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 79cfa06..ee5c638 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -101,6 +101,7 @@
import android.content.pm.ModuleInfo;
import android.content.pm.PackageChangeEvent;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInfoLite;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ComponentEnabledSetting;
@@ -129,6 +130,7 @@
import android.content.pm.VersionedPackage;
import android.content.pm.dex.IArtManager;
import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.PackageLite;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -189,7 +191,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.content.om.OverlayConfig;
import com.android.internal.telephony.CarrierAppUtils;
import com.android.internal.util.ArrayUtils;
@@ -245,6 +247,7 @@
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1;
import com.android.server.storage.DeviceStorageMonitorInternal;
+import com.android.server.supplementalprocess.SupplementalProcessManagerLocal;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.utils.Watchable;
@@ -932,6 +935,7 @@
final @Nullable String mOverlayConfigSignaturePackage;
final @Nullable String mRecentsPackage;
final @Nullable String mAmbientContextDetectionPackage;
+ private final @NonNull String mRequiredSupplementalProcessPackage;
@GuardedBy("mLock")
private final PackageUsage mPackageUsage = new PackageUsage();
@@ -1669,6 +1673,7 @@
mSharedSystemSharedLibraryPackageName = testParams.sharedSystemSharedLibraryPackageName;
mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage;
mResolveComponentName = testParams.resolveComponentName;
+ mRequiredSupplementalProcessPackage = testParams.requiredSupplementalProcessPackage;
mLiveComputer = createLiveComputer();
mSnapshotComputer = null;
@@ -1693,6 +1698,7 @@
mResolveIntentHelper = testParams.resolveIntentHelper;
mDexOptHelper = testParams.dexOptHelper;
mSuspendPackageHelper = testParams.suspendPackageHelper;
+
mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
registerObservers(false);
@@ -2136,6 +2142,9 @@
getPackageInfo(mRequiredPermissionControllerPackage, 0,
UserHandle.USER_SYSTEM).getLongVersionCode());
+ // Resolve the supplemental process
+ mRequiredSupplementalProcessPackage = getRequiredSupplementalProcessPackageName();
+
// Initialize InstantAppRegistry's Instant App list for all users.
for (AndroidPackage pkg : mPackages.values()) {
if (pkg.isSystem()) {
@@ -2901,6 +2910,36 @@
throw new IOException("Failed to free " + bytes + " on storage device at " + file);
}
+ int freeCacheForInstallation(int recommendedInstallLocation, PackageLite pkgLite,
+ String resolvedPath, String mPackageAbiOverride, int installFlags) {
+ // TODO: focus freeing disk space on the target device
+ final StorageManager storage = StorageManager.from(mContext);
+ final long lowThreshold = storage.getStorageLowBytes(Environment.getDataDirectory());
+
+ final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(resolvedPath,
+ mPackageAbiOverride);
+ if (sizeBytes >= 0) {
+ synchronized (mInstallLock) {
+ try {
+ mInstaller.freeCache(null, sizeBytes + lowThreshold, 0);
+ PackageInfoLite pkgInfoLite = PackageManagerServiceUtils.getMinimalPackageInfo(
+ mContext, pkgLite, resolvedPath, installFlags,
+ mPackageAbiOverride);
+ // The cache free must have deleted the file we downloaded to install.
+ if (pkgInfoLite.recommendedInstallLocation
+ == InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI) {
+ pkgInfoLite.recommendedInstallLocation =
+ InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+ }
+ return pkgInfoLite.recommendedInstallLocation;
+ } catch (Installer.InstallerException e) {
+ Slog.w(TAG, "Failed to free cache", e);
+ }
+ }
+ }
+ return recommendedInstallLocation;
+ }
+
/**
* Update given flags when being used to request {@link PackageInfo}.
*/
@@ -3121,6 +3160,11 @@
throw new IllegalStateException("PermissionController is not found");
}
+ @Override
+ public String getSupplementalProcessPackageName() {
+ return mRequiredSupplementalProcessPackage;
+ }
+
String getPackageInstallerPackageName() {
return mRequiredInstallerPackage;
}
@@ -3676,7 +3720,7 @@
// Before everything else, see whether we need to fstrim.
try {
- IStorageManager sm = PackageHelper.getStorageManager();
+ IStorageManager sm = InstallLocationUtils.getStorageManager();
if (sm != null) {
boolean doTrim = false;
final long interval = android.provider.Settings.Global.getLong(
@@ -5486,6 +5530,24 @@
}
}
+ private @NonNull String getRequiredSupplementalProcessPackageName() {
+ final Intent intent = new Intent(SupplementalProcessManagerLocal.SERVICE_INTERFACE);
+
+ final List<ResolveInfo> matches = queryIntentServicesInternal(
+ intent,
+ /* resolvedType= */ null,
+ MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ UserHandle.USER_SYSTEM,
+ /* callingUid= */ Process.myUid(),
+ /* includeInstantApps= */ false);
+ if (matches.size() == 1) {
+ return matches.get(0).getComponentInfo().packageName;
+ } else {
+ throw new RuntimeException("There should exactly one supplemental process; found "
+ + matches.size() + ": matches=" + matches);
+ }
+ }
+
@Override
public String getDefaultTextClassifierPackageName() {
return ensureSystemPackageName(
@@ -6595,8 +6657,9 @@
if (getInstallLocation() == loc) {
return true;
}
- if (loc == PackageHelper.APP_INSTALL_AUTO || loc == PackageHelper.APP_INSTALL_INTERNAL
- || loc == PackageHelper.APP_INSTALL_EXTERNAL) {
+ if (loc == InstallLocationUtils.APP_INSTALL_AUTO
+ || loc == InstallLocationUtils.APP_INSTALL_INTERNAL
+ || loc == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
android.provider.Settings.Global.putInt(mContext.getContentResolver(),
android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION, loc);
return true;
@@ -6609,7 +6672,7 @@
// allow instant app access
return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION,
- PackageHelper.APP_INSTALL_AUTO);
+ InstallLocationUtils.APP_INSTALL_AUTO);
}
/** Called by UserManagerService */
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 1caa76d..d12c826 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -89,6 +89,7 @@
public @Nullable String defaultTextClassifierPackage;
public @Nullable String systemTextClassifierPackage;
public @Nullable String overlayConfigSignaturePackage;
+ public @NonNull String requiredSupplementalProcessPackage;
public ViewCompiler viewCompiler;
public @Nullable String retailDemoPackage;
public @Nullable String recentsPackage;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 19c31e0..d6340b5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -43,6 +43,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageInfoLite;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackagePartitions;
import android.content.pm.ResolveInfo;
@@ -79,8 +80,8 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.content.NativeLibraryHelper;
-import com.android.internal.content.PackageHelper;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.HexDump;
@@ -805,27 +806,37 @@
final PackageInfoLite ret = new PackageInfoLite();
if (packagePath == null || pkg == null) {
Slog.i(TAG, "Invalid package file " + packagePath);
- ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
+ ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
return ret;
}
final File packageFile = new File(packagePath);
final long sizeBytes;
try {
- sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);
+ sizeBytes = InstallLocationUtils.calculateInstalledSize(pkg, abiOverride);
} catch (IOException e) {
if (!packageFile.exists()) {
- ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
+ ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI;
} else {
- ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
+ ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
}
return ret;
}
- final int recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
- pkg.getPackageName(), pkg.getInstallLocation(), sizeBytes, flags);
-
+ final PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_INVALID);
+ sessionParams.appPackageName = pkg.getPackageName();
+ sessionParams.installLocation = pkg.getInstallLocation();
+ sessionParams.sizeBytes = sizeBytes;
+ sessionParams.installFlags = flags;
+ final int recommendedInstallLocation;
+ try {
+ recommendedInstallLocation = InstallLocationUtils.resolveInstallLocation(context,
+ sessionParams);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
ret.packageName = pkg.getPackageName();
ret.splitNames = pkg.getSplitNames();
ret.versionCode = pkg.getVersionCode();
@@ -837,7 +848,6 @@
ret.recommendedInstallLocation = recommendedInstallLocation;
ret.multiArch = pkg.isMultiArch();
ret.debuggable = pkg.isDebuggable();
-
return ret;
}
@@ -857,7 +867,7 @@
throw new PackageManagerException(result.getErrorCode(),
result.getErrorMessage(), result.getException());
}
- return PackageHelper.calculateInstalledSize(result.getResult(), abiOverride);
+ return InstallLocationUtils.calculateInstalledSize(result.getResult(), abiOverride);
} catch (PackageManagerException | IOException e) {
Slog.w(TAG, "Failed to calculate installed size: " + e);
return -1;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 99f70b2..d4fcd06 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -98,7 +98,7 @@
import android.util.Slog;
import android.util.SparseArray;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
@@ -593,7 +593,7 @@
null /* splitApkPaths */, null /* splitRevisionCodes */,
apkLite.getTargetSdkVersion(), null /* requiredSplitTypes */,
null /* splitTypes */);
- sessionSize += PackageHelper.calculateInstalledSize(pkgLite,
+ sessionSize += InstallLocationUtils.calculateInstalledSize(pkgLite,
params.sessionParams.abiOverride, fd.getFileDescriptor());
} catch (IOException e) {
getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
@@ -922,7 +922,7 @@
final List<SharedLibraryInfo> libs = libsSlice.getList();
for (int l = 0, lsize = libs.size(); l < lsize; ++l) {
SharedLibraryInfo lib = libs.get(l);
- if (lib.getType() == SharedLibraryInfo.TYPE_SDK) {
+ if (lib.getType() == SharedLibraryInfo.TYPE_SDK_PACKAGE) {
name = lib.getName() + ":" + lib.getLongVersion();
break;
}
@@ -1649,11 +1649,11 @@
private int runGetInstallLocation() throws RemoteException {
int loc = mInterface.getInstallLocation();
String locStr = "invalid";
- if (loc == PackageHelper.APP_INSTALL_AUTO) {
+ if (loc == InstallLocationUtils.APP_INSTALL_AUTO) {
locStr = "auto";
- } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) {
+ } else if (loc == InstallLocationUtils.APP_INSTALL_INTERNAL) {
locStr = "internal";
- } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) {
+ } else if (loc == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
locStr = "external";
}
getOutPrintWriter().println(loc + "[" + locStr + "]");
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
index 9bfb7d1..9db215e 100644
--- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -28,7 +28,6 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.SigningDetails;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.rollback.RollbackInfo;
@@ -43,11 +42,12 @@
import android.util.apk.ApkSignatureVerifier;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
import com.android.server.rollback.RollbackManagerInternal;
import java.io.File;
@@ -291,8 +291,8 @@
throws PackageManagerException {
// Before marking the session as ready, start checkpoint service if available
try {
- if (PackageHelper.getStorageManager().supportsCheckpoint()) {
- PackageHelper.getStorageManager().startCheckpoint(2);
+ if (InstallLocationUtils.getStorageManager().supportsCheckpoint()) {
+ InstallLocationUtils.getStorageManager().startCheckpoint(2);
}
} catch (Exception e) {
// Failed to get hold of StorageManager
@@ -544,7 +544,7 @@
*/
private void checkActiveSessions() throws PackageManagerException {
try {
- checkActiveSessions(PackageHelper.getStorageManager().supportsCheckpoint());
+ checkActiveSessions(InstallLocationUtils.getStorageManager().supportsCheckpoint());
} catch (RemoteException e) {
throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED,
"Can't query fs-checkpoint status : " + e);
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 29de555..f63f8f4 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -53,7 +53,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
@@ -237,7 +237,7 @@
mApexManager.revertActiveSessions();
}
- PackageHelper.getStorageManager().abortChanges(
+ InstallLocationUtils.getStorageManager().abortChanges(
"abort-staged-install", false /*retry*/);
}
} catch (Exception e) {
@@ -674,8 +674,8 @@
boolean needsCheckpoint = false;
boolean supportsCheckpoint = false;
try {
- supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
- needsCheckpoint = PackageHelper.getStorageManager().needsCheckpoint();
+ supportsCheckpoint = InstallLocationUtils.getStorageManager().supportsCheckpoint();
+ needsCheckpoint = InstallLocationUtils.getStorageManager().needsCheckpoint();
} catch (RemoteException e) {
// This means that vold has crashed, and device is in a bad state.
throw new IllegalStateException("Failed to get checkpoint status", e);
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 1fa9013..9bcb724 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -285,6 +285,19 @@
);
/**
+ * User restrictions available to a device owner whose type is
+ * {@link android.app.admin.DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}.
+ */
+ private static final Set<String> FINANCED_DEVICE_OWNER_RESTRICTIONS = Sets.newArraySet(
+ UserManager.DISALLOW_ADD_USER,
+ UserManager.DISALLOW_DEBUGGING_FEATURES,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_SAFE_BOOT,
+ UserManager.DISALLOW_CONFIG_DATE_TIME,
+ UserManager.DISALLOW_OUTGOING_CALLS
+ );
+
+ /**
* Returns whether the given restriction name is valid (and logs it if it isn't).
*/
public static boolean isValidRestriction(@NonNull String restriction) {
@@ -458,6 +471,15 @@
}
/**
+ * @return {@code true} only if the restriction is allowed for financed devices and can be set
+ * by a device owner. Otherwise, {@code false} would be returned.
+ */
+ public static boolean canFinancedDeviceOwnerChange(String restriction) {
+ return FINANCED_DEVICE_OWNER_RESTRICTIONS.contains(restriction)
+ && canDeviceOwnerChange(restriction);
+ }
+
+ /**
* Whether given user restriction should be enforced globally.
*/
public static boolean isGlobal(@UserManagerInternal.OwnerType int restrictionOwnerType,
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 7e59bd6..f2b1a71 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -92,7 +92,7 @@
AndroidPackageUtils.getAllCodePaths(pkg),
pkg.getSdkLibName(),
pkg.getSdkLibVersionMajor(),
- SharedLibraryInfo.TYPE_SDK,
+ SharedLibraryInfo.TYPE_SDK_PACKAGE,
new VersionedPackage(pkg.getManifestPackageName(),
pkg.getLongVersionCode()),
null, null, false /* isNative */);
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 317730a..edc0e3d 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.pm.permission;
import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.Manifest.permission.UPDATE_APP_OPS_STATS;
import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
@@ -50,6 +51,7 @@
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;
@@ -608,6 +610,22 @@
}
@Override
+ public int checkPostNotificationsPermissionGrantedOrLegacyAccess(int uid) {
+ int granted = PermissionManagerService.this.checkUidPermission(uid,
+ POST_NOTIFICATIONS);
+ AndroidPackage pkg = mPackageManagerInt.getPackage(uid);
+ 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 PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ return granted;
+ }
+
+ @Override
public void startShellPermissionIdentityDelegation(int uid, @NonNull String packageName,
@Nullable List<String> permissionNames) {
Objects.requireNonNull(packageName, "packageName");
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index d2c4ec4..812d7a0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -63,6 +63,17 @@
int checkUidPermission(int uid, @NonNull String permissionName);
/**
+ * Check whether a particular UID has been granted the POST_NOTIFICATIONS permission, or if
+ * access should be granted based on legacy access (currently symbolized by the REVIEW_REQUIRED
+ * permission flag
+ *
+ * @param uid the UID
+ * @return {@code PERMISSION_GRANTED} if the permission is granted, or legacy access is granted,
+ * {@code PERMISSION_DENIED} otherwise
+ */
+ int checkPostNotificationsPermissionGrantedOrLegacyAccess(int uid);
+
+ /**
* Adds a listener for runtime permission state (permissions or flags) changes.
*
* @param listener The listener.
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
index 586d2c4..cf478b1 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
@@ -34,4 +34,7 @@
@Nullable
String getMaxSdkVersion();
+
+ int getInitOrder();
+
}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
index 1e427d0..167aba3 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
@@ -48,18 +48,18 @@
@Nullable
private String maxSdkVersion;
+ private int initOrder;
+
public ParsedApexSystemServiceImpl() {
}
-
-
// Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -71,13 +71,15 @@
@NonNull String name,
@Nullable String jarPath,
@Nullable String minSdkVersion,
- @Nullable String maxSdkVersion) {
+ @Nullable String maxSdkVersion,
+ int initOrder) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
this.jarPath = jarPath;
this.minSdkVersion = minSdkVersion;
this.maxSdkVersion = maxSdkVersion;
+ this.initOrder = initOrder;
// onConstructed(); // You can define this method to get a callback
}
@@ -103,6 +105,11 @@
}
@DataClass.Generated.Member
+ public int getInitOrder() {
+ return initOrder;
+ }
+
+ @DataClass.Generated.Member
public @NonNull ParsedApexSystemServiceImpl setName(@NonNull String value) {
name = value;
com.android.internal.util.AnnotationValidations.validate(
@@ -129,6 +136,12 @@
}
@DataClass.Generated.Member
+ public @NonNull ParsedApexSystemServiceImpl setInitOrder( int value) {
+ initOrder = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
static Parcelling<String> sParcellingForName =
Parcelling.Cache.get(
Parcelling.BuiltIn.ForInternedString.class);
@@ -187,6 +200,7 @@
sParcellingForJarPath.parcel(jarPath, dest, flags);
sParcellingForMinSdkVersion.parcel(minSdkVersion, dest, flags);
sParcellingForMaxSdkVersion.parcel(maxSdkVersion, dest, flags);
+ dest.writeInt(initOrder);
}
@Override
@@ -205,6 +219,7 @@
String _jarPath = sParcellingForJarPath.unparcel(in);
String _minSdkVersion = sParcellingForMinSdkVersion.unparcel(in);
String _maxSdkVersion = sParcellingForMaxSdkVersion.unparcel(in);
+ int _initOrder = in.readInt();
this.name = _name;
com.android.internal.util.AnnotationValidations.validate(
@@ -212,6 +227,7 @@
this.jarPath = _jarPath;
this.minSdkVersion = _minSdkVersion;
this.maxSdkVersion = _maxSdkVersion;
+ this.initOrder = _initOrder;
// onConstructed(); // You can define this method to get a callback
}
@@ -231,10 +247,10 @@
};
@DataClass.Generated(
- time = 1641431950080L,
+ time = 1643723578605L,
codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java",
- inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java",
+ inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.server.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
index 38a6f5a35..ed9aa2e 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
@@ -53,10 +53,13 @@
R.styleable.AndroidManifestApexSystemService_minSdkVersion);
String maxSdkVersion = sa.getString(
R.styleable.AndroidManifestApexSystemService_maxSdkVersion);
+ int initOrder = sa.getInt(R.styleable.AndroidManifestApexSystemService_initOrder, 0);
systemService.setName(className)
.setMinSdkVersion(minSdkVersion)
- .setMaxSdkVersion(maxSdkVersion);
+ .setMaxSdkVersion(maxSdkVersion)
+ .setInitOrder(initOrder);
+
if (!TextUtils.isEmpty(jarPath)) {
systemService.setJarPath(jarPath);
}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 73ec2cd..77d6310 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -33,6 +33,7 @@
import android.net.Uri;
import android.os.BatteryStats;
import android.os.Handler;
+import android.os.IWakeLockCallback;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
@@ -215,14 +216,15 @@
* Called when a wake lock is acquired.
*/
public void onWakeLockAcquired(int flags, String tag, String packageName,
- int ownerUid, int ownerPid, WorkSource workSource, String historyTag) {
+ int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
+ IWakeLockCallback callback) {
if (DEBUG) {
Slog.d(TAG, "onWakeLockAcquired: flags=" + flags + ", tag=\"" + tag
+ "\", packageName=" + packageName
+ ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ ", workSource=" + workSource);
}
-
+ notifyWakeLockListener(callback, true);
final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
if (monitorType >= 0) {
try {
@@ -300,8 +302,9 @@
*/
public void onWakeLockChanging(int flags, String tag, String packageName,
int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
- int newFlags, String newTag, String newPackageName, int newOwnerUid,
- int newOwnerPid, WorkSource newWorkSource, String newHistoryTag) {
+ IWakeLockCallback callback, int newFlags, String newTag, String newPackageName,
+ int newOwnerUid, int newOwnerPid, WorkSource newWorkSource, String newHistoryTag,
+ IWakeLockCallback newCallback) {
final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
final int newMonitorType = getBatteryStatsWakeLockMonitorType(newFlags);
@@ -323,10 +326,16 @@
} catch (RemoteException ex) {
// Ignore
}
- } else {
- onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag);
+ } else if (!PowerManagerService.isSameCallback(callback, newCallback)) {
+ onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag,
+ null /* Do not notify the old callback */);
onWakeLockAcquired(newFlags, newTag, newPackageName, newOwnerUid, newOwnerPid,
- newWorkSource, newHistoryTag);
+ newWorkSource, newHistoryTag, newCallback /* notify the new callback */);
+ } else {
+ onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag,
+ callback);
+ onWakeLockAcquired(newFlags, newTag, newPackageName, newOwnerUid, newOwnerPid,
+ newWorkSource, newHistoryTag, newCallback);
}
}
@@ -334,14 +343,15 @@
* Called when a wake lock is released.
*/
public void onWakeLockReleased(int flags, String tag, String packageName,
- int ownerUid, int ownerPid, WorkSource workSource, String historyTag) {
+ int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
+ IWakeLockCallback callback) {
if (DEBUG) {
Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag
+ "\", packageName=" + packageName
+ ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ ", workSource=" + workSource);
}
-
+ notifyWakeLockListener(callback, false);
final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
if (monitorType >= 0) {
try {
@@ -859,6 +869,18 @@
return enabled && dndOff;
}
+ private void notifyWakeLockListener(IWakeLockCallback callback, boolean isEnabled) {
+ if (callback != null) {
+ mHandler.post(() -> {
+ try {
+ callback.onStateChanged(isEnabled);
+ } catch (RemoteException e) {
+ throw new IllegalArgumentException("Wakelock.mCallback is already dead.", e);
+ }
+ });
+ }
+ }
+
private final class NotifierHandler extends Handler {
public NotifierHandler(Looper looper) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index efcfbdd..3857072 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -66,6 +66,7 @@
import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IPowerManager;
+import android.os.IWakeLockCallback;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelDuration;
@@ -1436,7 +1437,8 @@
}
private void acquireWakeLockInternal(IBinder lock, int displayId, int flags, String tag,
- String packageName, WorkSource ws, String historyTag, int uid, int pid) {
+ String packageName, WorkSource ws, String historyTag, int uid, int pid,
+ @Nullable IWakeLockCallback callback) {
synchronized (mLock) {
if (displayId != Display.INVALID_DISPLAY) {
final DisplayInfo displayInfo =
@@ -1460,11 +1462,12 @@
boolean notifyAcquire;
if (index >= 0) {
wakeLock = mWakeLocks.get(index);
- if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) {
+ if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid, callback)) {
// Update existing wake lock. This shouldn't happen but is harmless.
notifyWakeLockChangingLocked(wakeLock, flags, tag, packageName,
- uid, pid, ws, historyTag);
- wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid);
+ uid, pid, ws, historyTag, callback);
+ wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid,
+ callback);
}
notifyAcquire = false;
} else {
@@ -1476,12 +1479,7 @@
}
state.mNumWakeLocks++;
wakeLock = new WakeLock(lock, displayId, flags, tag, packageName, ws, historyTag,
- uid, pid, state);
- try {
- lock.linkToDeath(wakeLock, 0);
- } catch (RemoteException ex) {
- throw new IllegalArgumentException("Wake lock is already dead.");
- }
+ uid, pid, state, callback);
mWakeLocks.add(wakeLock);
setWakeLockDisabledStateLocked(wakeLock);
notifyAcquire = true;
@@ -1576,11 +1574,8 @@
mRequestWaitForNegativeProximity = true;
}
- try {
- wakeLock.mLock.unlinkToDeath(wakeLock, 0);
- } catch (NoSuchElementException e) {
- Slog.wtf(TAG, "Failed to unlink wakelock", e);
- }
+ wakeLock.unlinkToDeath();
+ wakeLock.setDisabled(true);
removeWakeLockLocked(wakeLock, index);
}
}
@@ -1650,13 +1645,41 @@
if (!wakeLock.hasSameWorkSource(ws)) {
notifyWakeLockChangingLocked(wakeLock, wakeLock.mFlags, wakeLock.mTag,
wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
- ws, historyTag);
+ ws, historyTag, null);
wakeLock.mHistoryTag = historyTag;
wakeLock.updateWorkSource(ws);
}
}
}
+ private void updateWakeLockCallbackInternal(IBinder lock, IWakeLockCallback callback,
+ int callingUid) {
+ synchronized (mLock) {
+ int index = findWakeLockIndexLocked(lock);
+ if (index < 0) {
+ if (DEBUG_SPEW) {
+ Slog.d(TAG, "updateWakeLockCallbackInternal: lock=" + Objects.hashCode(lock)
+ + " [not found]");
+ }
+ throw new IllegalArgumentException("Wake lock not active: " + lock
+ + " from uid " + callingUid);
+ }
+
+ WakeLock wakeLock = mWakeLocks.get(index);
+ if (DEBUG_SPEW) {
+ Slog.d(TAG, "updateWakeLockCallbackInternal: lock=" + Objects.hashCode(lock)
+ + " [" + wakeLock.mTag + "]");
+ }
+
+ if (!isSameCallback(callback, wakeLock.mCallback)) {
+ notifyWakeLockChangingLocked(wakeLock, wakeLock.mFlags, wakeLock.mTag,
+ wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
+ wakeLock.mWorkSource, wakeLock.mHistoryTag, callback);
+ wakeLock.mCallback = callback;
+ }
+ }
+ }
+
@GuardedBy("mLock")
private int findWakeLockIndexLocked(IBinder lock) {
final int count = mWakeLocks.size();
@@ -1684,7 +1707,7 @@
wakeLock.mNotifiedAcquired = true;
mNotifier.onWakeLockAcquired(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName,
wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource,
- wakeLock.mHistoryTag);
+ wakeLock.mHistoryTag, wakeLock.mCallback);
restartNofifyLongTimerLocked(wakeLock);
}
}
@@ -1726,11 +1749,13 @@
@GuardedBy("mLock")
private void notifyWakeLockChangingLocked(WakeLock wakeLock, int flags, String tag,
- String packageName, int uid, int pid, WorkSource ws, String historyTag) {
+ String packageName, int uid, int pid, WorkSource ws, String historyTag,
+ IWakeLockCallback callback) {
if (mSystemReady && wakeLock.mNotifiedAcquired) {
mNotifier.onWakeLockChanging(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName,
wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource,
- wakeLock.mHistoryTag, flags, tag, packageName, uid, pid, ws, historyTag);
+ wakeLock.mHistoryTag, wakeLock.mCallback, flags, tag, packageName, uid, pid, ws,
+ historyTag, callback);
notifyWakeLockLongFinishedLocked(wakeLock);
// Changing the wake lock will count as releasing the old wake lock(s) and
// acquiring the new ones... we do this because otherwise once a wakelock
@@ -1747,7 +1772,7 @@
wakeLock.mAcquireTime = 0;
mNotifier.onWakeLockReleased(wakeLock.mFlags, wakeLock.mTag,
wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
- wakeLock.mWorkSource, wakeLock.mHistoryTag);
+ wakeLock.mWorkSource, wakeLock.mHistoryTag, wakeLock.mCallback);
notifyWakeLockLongFinishedLocked(wakeLock);
}
}
@@ -4045,10 +4070,7 @@
}
}
}
- if (wakeLock.mDisabled != disabled) {
- wakeLock.mDisabled = disabled;
- return true;
- }
+ return wakeLock.setDisabled(disabled);
}
return false;
}
@@ -5041,10 +5063,11 @@
public boolean mNotifiedAcquired;
public boolean mNotifiedLong;
public boolean mDisabled;
+ public IWakeLockCallback mCallback;
public WakeLock(IBinder lock, int displayId, int flags, String tag, String packageName,
WorkSource workSource, String historyTag, int ownerUid, int ownerPid,
- UidState uidState) {
+ UidState uidState, @Nullable IWakeLockCallback callback) {
mLock = lock;
mDisplayId = displayId;
mFlags = flags;
@@ -5055,15 +5078,43 @@
mOwnerUid = ownerUid;
mOwnerPid = ownerPid;
mUidState = uidState;
+ mCallback = callback;
+ linkToDeath();
}
@Override
public void binderDied() {
+ unlinkToDeath();
PowerManagerService.this.handleWakeLockDeath(this);
}
+ private void linkToDeath() {
+ try {
+ mLock.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ throw new IllegalArgumentException("Wakelock.mLock is already dead.");
+ }
+ }
+
+ @GuardedBy("mLock")
+ void unlinkToDeath() {
+ try {
+ mLock.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.wtf(TAG, "Failed to unlink Wakelock.mLock", e);
+ }
+ }
+
+ public boolean setDisabled(boolean disabled) {
+ if (mDisabled != disabled) {
+ mDisabled = disabled;
+ return true;
+ } else {
+ return false;
+ }
+ }
public boolean hasSameProperties(int flags, String tag, WorkSource workSource,
- int ownerUid, int ownerPid) {
+ int ownerUid, int ownerPid, IWakeLockCallback callback) {
return mFlags == flags
&& mTag.equals(tag)
&& hasSameWorkSource(workSource)
@@ -5072,7 +5123,8 @@
}
public void updateProperties(int flags, String tag, String packageName,
- WorkSource workSource, String historyTag, int ownerUid, int ownerPid) {
+ WorkSource workSource, String historyTag, int ownerUid, int ownerPid,
+ IWakeLockCallback callback) {
if (!mPackageName.equals(packageName)) {
throw new IllegalStateException("Existing wake lock package name changed: "
+ mPackageName + " to " + packageName);
@@ -5089,6 +5141,7 @@
mTag = tag;
updateWorkSource(workSource);
mHistoryTag = historyTag;
+ mCallback = callback;
}
public boolean hasSameWorkSource(WorkSource workSource) {
@@ -5307,11 +5360,12 @@
@Override // Binder call
public void acquireWakeLockWithUid(IBinder lock, int flags, String tag,
- String packageName, int uid, int displayId) {
+ String packageName, int uid, int displayId, IWakeLockCallback callback) {
if (uid < 0) {
uid = Binder.getCallingUid();
}
- acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid), null, displayId);
+ acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid), null,
+ displayId, callback);
}
@Override // Binder call
@@ -5346,7 +5400,8 @@
@Override // Binder call
public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
- WorkSource ws, String historyTag, int displayId) {
+ WorkSource ws, String historyTag, int displayId,
+ @Nullable IWakeLockCallback callback) {
if (lock == null) {
throw new IllegalArgumentException("lock must not be null");
}
@@ -5386,7 +5441,7 @@
final long ident = Binder.clearCallingIdentity();
try {
acquireWakeLockInternal(lock, displayId, flags, tag, packageName, ws, historyTag,
- uid, pid);
+ uid, pid, callback);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -5395,7 +5450,8 @@
@Override // Binder call
public void acquireWakeLockAsync(IBinder lock, int flags, String tag, String packageName,
WorkSource ws, String historyTag) {
- acquireWakeLock(lock, flags, tag, packageName, ws, historyTag, Display.INVALID_DISPLAY);
+ acquireWakeLock(lock, flags, tag, packageName, ws, historyTag, Display.INVALID_DISPLAY,
+ null);
}
@Override // Binder call
@@ -5463,6 +5519,23 @@
}
@Override // Binder call
+ public void updateWakeLockCallback(IBinder lock, IWakeLockCallback callback) {
+ if (lock == null) {
+ throw new IllegalArgumentException("lock must not be null");
+ }
+
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+
+ final int callingUid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ updateWakeLockCallbackInternal(lock, callback, callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
public boolean isWakeLockLevelSupported(int level) {
final long ident = Binder.clearCallingIdentity();
try {
@@ -6510,4 +6583,15 @@
}
};
+ static boolean isSameCallback(IWakeLockCallback callback1,
+ IWakeLockCallback callback2) {
+ if (callback1 == callback2) {
+ return true;
+ }
+ if (callback1 != null && callback2 != null
+ && callback1.asBinder() == callback2.asBinder()) {
+ return true;
+ }
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerService.java b/services/core/java/com/android/server/resources/ResourcesManagerService.java
new file mode 100644
index 0000000..cc27546
--- /dev/null
+++ b/services/core/java/com/android/server/resources/ResourcesManagerService.java
@@ -0,0 +1,102 @@
+/*
+ * 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.resources;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.IResourcesManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+
+import com.android.server.SystemService;
+import com.android.server.am.ActivityManagerService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * A service for managing information about ResourcesManagers
+ */
+public class ResourcesManagerService extends SystemService {
+ private ActivityManagerService mActivityManagerService;
+
+ /**
+ * Initializes the system service.
+ * <p>
+ * Subclasses must define a single argument constructor that accepts the context
+ * and passes it to super.
+ * </p>
+ *
+ * @param context The system server context.
+ */
+ public ResourcesManagerService(@NonNull Context context) {
+ super(context);
+ publishBinderService(Context.RESOURCES_SERVICE, mService);
+ }
+
+ @Override
+ public void onStart() {
+ // Intentionally left empty.
+ }
+
+ private final IBinder mService = new IResourcesManager.Stub() {
+ @Override
+ public boolean dumpResources(String process, ParcelFileDescriptor fd,
+ RemoteCallback callback) throws RemoteException {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
+ callback.sendResult(null);
+ throw new SecurityException("dump should only be called by shell");
+ }
+ return mActivityManagerService.dumpResources(process, fd, callback);
+ }
+
+ @Override
+ protected void dump(@NonNull FileDescriptor fd,
+ @NonNull PrintWriter pw, @Nullable String[] args) {
+ try {
+ mActivityManagerService.dumpAllResources(ParcelFileDescriptor.dup(fd), pw);
+ } catch (Exception e) {
+ pw.println("Exception while trying to dump all resources: " + e.getMessage());
+ e.printStackTrace(pw);
+ }
+ }
+
+ @Override
+ public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out,
+ @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+ return (new ResourcesManagerShellCommand(this)).exec(
+ this,
+ in.getFileDescriptor(),
+ out.getFileDescriptor(),
+ err.getFileDescriptor(),
+ args);
+ }
+ };
+
+ public void setActivityManagerService(
+ ActivityManagerService activityManagerService) {
+ mActivityManagerService = activityManagerService;
+ }
+}
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
new file mode 100644
index 0000000..7d8336a
--- /dev/null
+++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
@@ -0,0 +1,94 @@
+/*
+ * 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.resources;
+
+import android.content.res.IResourcesManager;
+import android.os.ConditionVariable;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Shell command handler for resources related commands
+ */
+public class ResourcesManagerShellCommand extends ShellCommand {
+ private static final String TAG = "ResourcesManagerShellCommand";
+
+ private final IResourcesManager mInterface;
+
+ public ResourcesManagerShellCommand(IResourcesManager anInterface) {
+ mInterface = anInterface;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter err = getErrPrintWriter();
+ try {
+ switch (cmd) {
+ case "dump":
+ return dumpResources();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (IllegalArgumentException e) {
+ err.println("Error: " + e.getMessage());
+ } catch (RemoteException e) {
+ err.println("Remote exception: " + e);
+ }
+ return -1;
+ }
+
+ private int dumpResources() throws RemoteException {
+ String processId = getNextArgRequired();
+ try {
+ ConditionVariable lock = new ConditionVariable();
+ RemoteCallback
+ finishCallback = new RemoteCallback(result -> lock.open(), null);
+
+ if (!mInterface.dumpResources(processId,
+ ParcelFileDescriptor.dup(getOutFileDescriptor()), finishCallback)) {
+ getErrPrintWriter().println("RESOURCES DUMP FAILED on process " + processId);
+ return -1;
+ }
+ lock.block(5000);
+ return 0;
+ } catch (IOException e) {
+ Slog.e(TAG, "Exception while dumping resources", e);
+ getErrPrintWriter().println("Exception while dumping resources: " + e.getMessage());
+ }
+ return -1;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter out = getOutPrintWriter();
+ out.println("Resources manager commands:");
+ out.println(" help");
+ out.println(" Print this help text.");
+ out.println(" dump <PROCESS>");
+ out.println(" Dump the Resources objects in use as well as the history of Resources");
+
+ }
+}
diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
index f519ced..243efb5 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 @@
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 0000000..58df2bd
--- /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 e71ff78..94f483c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -47,11 +47,13 @@
import android.graphics.drawable.Icon;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
+import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
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;
@@ -91,6 +93,7 @@
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;
@@ -157,6 +160,8 @@
private final SparseArray<UiState> mDisplayUiState = new SparseArray<>();
@GuardedBy("mLock")
private IUdfpsHbmListener mUdfpsHbmListener;
+ @GuardedBy("mLock")
+ private IBiometricContextListener mBiometricContextListener;
@GuardedBy("mCurrentRequestAddTilePackages")
private final ArrayMap<String, Long> mCurrentRequestAddTilePackages = new ArrayMap<>();
@@ -894,6 +899,20 @@
}
@Override
+ public void setBiometicContextListener(IBiometricContextListener listener) {
+ enforceStatusBarService();
+ synchronized (mLock) {
+ mBiometricContextListener = listener;
+ }
+ if (mBar != null) {
+ try {
+ mBar.setBiometicContextListener(listener);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ @Override
public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
enforceStatusBarService();
if (mBar != null) {
@@ -1248,6 +1267,12 @@
"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.
@@ -1315,6 +1340,7 @@
mHandler.post(() -> {
synchronized (mLock) {
setUdfpsHbmListener(mUdfpsHbmListener);
+ setBiometicContextListener(mBiometricContextListener);
}
});
}
@@ -1969,6 +1995,53 @@
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/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 06ce4a4..1dea3d7 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -71,6 +71,7 @@
private static final int MSG_ESCROW_TOKEN_STATE = 9;
private static final int MSG_UNLOCK_USER = 10;
private static final int MSG_SHOW_KEYGUARD_ERROR_MESSAGE = 11;
+ private static final int MSG_LOCK_USER = 12;
/**
* Time in uptime millis that we wait for the service connection, both when starting
@@ -100,6 +101,8 @@
// Trust state
private boolean mTrusted;
+ private boolean mWaitingForTrustableDowngrade = false;
+ private boolean mTrustable;
private CharSequence mMessage;
private boolean mDisplayTrustGrantedMessage;
private boolean mTrustDisabledByDpm;
@@ -108,6 +111,25 @@
private AlarmManager mAlarmManager;
private final Intent mAlarmIntent;
private PendingIntent mAlarmPendingIntent;
+ private final BroadcastReceiver mTrustableDowngradeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!TrustManagerService.ENABLE_ACTIVE_UNLOCK_FLAG) {
+ return;
+ }
+ if (!mWaitingForTrustableDowngrade) {
+ return;
+ }
+ // are these the broadcasts we want to listen to
+ if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())
+ || Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
+ mTrusted = false;
+ mTrustable = true;
+ mWaitingForTrustableDowngrade = false;
+ mTrustManagerService.updateTrust(mUserId, 0);
+ }
+ }
+ };
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -126,16 +148,21 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_GRANT_TRUST:
- // TODO(b/213631675): Respect FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE
if (!isConnected()) {
Log.w(TAG, "Agent is not connected, cannot grant trust: "
+ mName.flattenToShortString());
return;
}
mTrusted = true;
+ mTrustable = false;
mMessage = (CharSequence) msg.obj;
int flags = msg.arg1;
mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
+ if ((flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) {
+ mWaitingForTrustableDowngrade = true;
+ } else {
+ mWaitingForTrustableDowngrade = false;
+ }
long durationMs = msg.getData().getLong(DATA_DURATION);
if (durationMs > 0) {
final long duration;
@@ -270,6 +297,13 @@
mTrustManagerService.showKeyguardErrorMessage(message);
break;
}
+ case MSG_LOCK_USER: {
+ mTrusted = false;
+ mTrustable = false;
+ mTrustManagerService.updateTrust(mUserId, 0 /* flags */);
+ mTrustManagerService.lockUser(mUserId);
+ break;
+ }
}
}
};
@@ -295,6 +329,11 @@
}
@Override
+ public void lockUser() {
+ mHandler.sendEmptyMessage(MSG_LOCK_USER);
+ }
+
+ @Override
public void setManagingTrust(boolean managingTrust) {
if (DEBUG) Slog.d(TAG, "managingTrust()");
mHandler.obtainMessage(MSG_MANAGING_TRUST, managingTrust ? 1 : 0, 0).sendToTarget();
@@ -427,6 +466,9 @@
final String pathUri = mAlarmIntent.toUri(Intent.URI_INTENT_SCHEME);
alarmFilter.addDataPath(pathUri, PatternMatcher.PATTERN_LITERAL);
+ IntentFilter trustableFilter = new IntentFilter(Intent.ACTION_USER_PRESENT);
+ trustableFilter.addAction(Intent.ACTION_SCREEN_OFF);
+
// Schedules a restart for when connecting times out. If the connection succeeds,
// the restart is canceled in mCallback's onConnected.
scheduleRestart();
@@ -435,6 +477,7 @@
if (mBound) {
mContext.registerReceiver(mBroadcastReceiver, alarmFilter, PERMISSION, null,
Context.RECEIVER_EXPORTED);
+ mContext.registerReceiver(mTrustableDowngradeReceiver, trustableFilter);
} else {
Log.e(TAG, "Can't bind to TrustAgent " + mName.flattenToShortString());
}
@@ -591,6 +634,10 @@
return mTrusted && mManagingTrust && !mTrustDisabledByDpm;
}
+ public boolean isTrustable() {
+ return mTrustable && mManagingTrust && !mTrustDisabledByDpm;
+ }
+
public boolean isManagingTrust() {
return mManagingTrust && !mTrustDisabledByDpm;
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 9bed24d..6aafd4a 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -54,6 +54,7 @@
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -65,6 +66,7 @@
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.Xml;
import android.view.IWindowManager;
@@ -145,6 +147,21 @@
@GuardedBy("mUserIsTrusted")
private final SparseBooleanArray mUserIsTrusted = new SparseBooleanArray();
+ //TODO(b/215724686): remove flag
+ public static final boolean ENABLE_ACTIVE_UNLOCK_FLAG = SystemProperties.getBoolean(
+ "fw.enable_active_unlock_flag", true);
+
+ private enum TrustState {
+ UNTRUSTED, // the phone is not unlocked by any trustagents
+ TRUSTABLE, // the phone is in a semi-locked state that can be unlocked if
+ // FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE is passed and a trustagent is trusted
+ TRUSTED // the phone is unlocked
+ };
+
+ @GuardedBy("mUserTrustState")
+ private final SparseArray<TrustManagerService.TrustState> mUserTrustState =
+ new SparseArray<>();
+
/**
* Stores the locked state for users on the device. There are three different type of users
* which are handled slightly differently:
@@ -228,7 +245,6 @@
}
// Extend unlock config and logic
-
private final class SettingsObserver extends ContentObserver {
private final Uri TRUST_AGENTS_EXTEND_UNLOCK =
Settings.Secure.getUriFor(Settings.Secure.TRUST_AGENTS_EXTEND_UNLOCK);
@@ -396,6 +412,14 @@
}
private void updateTrust(int userId, int flags, boolean isFromUnlock) {
+ if (ENABLE_ACTIVE_UNLOCK_FLAG) {
+ updateTrustWithRenewableUnlock(userId, flags, isFromUnlock);
+ } else {
+ updateTrustWithExtendUnlock(userId, flags, isFromUnlock);
+ }
+ }
+
+ private void updateTrustWithExtendUnlock(int userId, int flags, boolean isFromUnlock) {
boolean managed = aggregateIsTrustManaged(userId);
dispatchOnTrustManagedChanged(managed, userId);
if (mStrongAuthTracker.isTrustAllowedForUser(userId)
@@ -441,6 +465,65 @@
}
}
+ private void updateTrustWithRenewableUnlock(int userId, int flags, boolean isFromUnlock) {
+ boolean managed = aggregateIsTrustManaged(userId);
+ dispatchOnTrustManagedChanged(managed, userId);
+ if (mStrongAuthTracker.isTrustAllowedForUser(userId)
+ && isTrustUsuallyManagedInternal(userId) != managed) {
+ updateTrustUsuallyManaged(userId, managed);
+ }
+
+ boolean trustedByAtLeastOneAgent = aggregateIsTrusted(userId);
+ boolean trustableByAtLeastOneAgent = aggregateIsTrustable(userId);
+ boolean wasTrusted;
+ boolean wasTrustable;
+ TrustState pendingTrustState;
+
+ IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ boolean alreadyUnlocked = false;
+ try {
+ alreadyUnlocked = !wm.isKeyguardLocked();
+ } catch (RemoteException e) {
+ }
+
+ synchronized (mUserTrustState) {
+ wasTrusted = (mUserTrustState.get(userId) == TrustState.TRUSTED);
+ wasTrustable = (mUserTrustState.get(userId) == TrustState.TRUSTABLE);
+ boolean renewingTrust = wasTrustable && (
+ (flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0);
+ boolean canMoveToTrusted = alreadyUnlocked || isFromUnlock || renewingTrust;
+ boolean upgradingTrustForCurrentUser = (userId == mCurrentUser);
+
+ if (trustedByAtLeastOneAgent && wasTrusted) {
+ // no change
+ return;
+ } else if (trustedByAtLeastOneAgent && canMoveToTrusted
+ && upgradingTrustForCurrentUser) {
+ pendingTrustState = TrustState.TRUSTED;
+ } else if (trustableByAtLeastOneAgent && (wasTrusted || wasTrustable)
+ && upgradingTrustForCurrentUser) {
+ pendingTrustState = TrustState.TRUSTABLE;
+ } else {
+ pendingTrustState = TrustState.UNTRUSTED;
+ }
+
+ mUserTrustState.put(userId, pendingTrustState);
+ }
+ if (DEBUG) Slog.d(TAG, "pendingTrustState: " + pendingTrustState);
+
+ boolean isNowTrusted = pendingTrustState == TrustState.TRUSTED;
+ dispatchOnTrustChanged(isNowTrusted, userId, flags, getTrustGrantedMessages(userId));
+ if (isNowTrusted != wasTrusted) {
+ refreshDeviceLockedForUser(userId);
+ if (!isNowTrusted) {
+ maybeLockScreen(userId);
+ } else {
+ scheduleTrustTimeout(userId, false /* override */);
+ }
+ }
+ }
+
+
private void updateTrustUsuallyManaged(int userId, boolean managed) {
synchronized (mTrustUsuallyManagedForUser) {
mTrustUsuallyManagedForUser.put(userId, managed);
@@ -472,6 +555,20 @@
mLockPatternUtils.unlockUserWithToken(handle, token, userId);
}
+ /**
+ * Locks the phone and requires some auth (not trust) like a biometric or passcode before
+ * unlocking.
+ */
+ public void lockUser(int userId) {
+ mLockPatternUtils.requireStrongAuth(
+ StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId);
+ try {
+ WindowManagerGlobal.getWindowManagerService().lockNow(null);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error locking screen when called from trust agent");
+ }
+ }
+
void showKeyguardErrorMessage(CharSequence message) {
dispatchOnTrustError(message);
}
@@ -950,6 +1047,21 @@
return false;
}
+ private boolean aggregateIsTrustable(int userId) {
+ if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
+ return false;
+ }
+ for (int i = 0; i < mActiveAgents.size(); i++) {
+ AgentInfo info = mActiveAgents.valueAt(i);
+ if (info.userId == userId) {
+ if (info.agent.isTrustable()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private List<String> getTrustGrantedMessages(int userId) {
if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
return new ArrayList<>();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3908874..5f04b7e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -46,6 +46,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_HOME;
import static android.content.Intent.CATEGORY_LAUNCHER;
@@ -241,6 +244,7 @@
import android.app.TaskInfo.CameraCompatControlState;
import android.app.WaitResult;
import android.app.WindowConfiguration;
+import android.app.admin.DevicePolicyManager;
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityRelaunchItem;
@@ -272,6 +276,7 @@
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
import android.net.Uri;
import android.os.Binder;
@@ -538,6 +543,15 @@
// Tracking splash screen status from previous activity
boolean mSplashScreenStyleEmpty = false;
+ Drawable mEnterpriseThumbnailDrawable;
+
+ private void updateEnterpriseThumbnailDrawable(Context context) {
+ DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ mEnterpriseThumbnailDrawable = dpm.getDrawable(
+ WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION,
+ () -> context.getDrawable(R.drawable.ic_corp_badge));
+ }
+
static final int LAUNCH_SOURCE_TYPE_SYSTEM = 1;
static final int LAUNCH_SOURCE_TYPE_HOME = 2;
static final int LAUNCH_SOURCE_TYPE_SYSTEMUI = 3;
@@ -716,6 +730,11 @@
@Nullable
private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
+ // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
+ // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
+ // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
+ private boolean mIsEligibleForFixedOrientationLetterbox;
+
// State of the Camera app compat control which is used to correct stretched viewfinder
// in apps that don't handle all possible configurations and changes between them correctly.
@CameraCompatControlState
@@ -1925,6 +1944,8 @@
mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
mActivityRecordInputSink = new ActivityRecordInputSink(this);
+
+ updateEnterpriseThumbnailDrawable(mAtmService.mUiContext);
}
/**
@@ -6929,7 +6950,7 @@
@Override
void prepareSurfaces() {
- final boolean show = isVisible() || isAnimating(TRANSITION | PARENTS,
+ final boolean show = isVisible() || isAnimating(PARENTS,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
if (mSurfaceControl != null) {
@@ -6988,12 +7009,11 @@
return;
}
final Rect frame = win.getRelativeFrame();
- final int thumbnailDrawableRes = task.mUserId == mWmService.mCurrentUserId
- ? R.drawable.ic_account_circle
- : R.drawable.ic_corp_badge;
- final HardwareBuffer thumbnail =
- getDisplayContent().mAppTransition
- .createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
+ final Drawable thumbnailDrawable = task.mUserId == mWmService.mCurrentUserId
+ ? mAtmService.mUiContext.getDrawable(R.drawable.ic_account_circle)
+ : mEnterpriseThumbnailDrawable;
+ final HardwareBuffer thumbnail = getDisplayContent().mAppTransition
+ .createCrossProfileAppsThumbnail(thumbnailDrawable, frame);
if (thumbnail == null) {
return;
}
@@ -7627,6 +7647,24 @@
}
/**
+ * Whether this activity is eligible for letterbox eduction.
+ *
+ * <p>Conditions that need to be met:
+ *
+ * <ul>
+ * <li>{@link LetterboxConfiguration#getIsEducationEnabled} is true.
+ * <li>The activity is eligible for fixed orientation letterbox.
+ * <li>The activity is in fullscreen.
+ * </ul>
+ */
+ // TODO(b/215316431): Add tests
+ boolean isEligibleForLetterboxEducation() {
+ return mWmService.mLetterboxConfiguration.getIsEducationEnabled()
+ && mIsEligibleForFixedOrientationLetterbox
+ && getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ }
+
+ /**
* In some cases, applying insets to bounds changes the orientation. For example, if a
* close-to-square display rotates to portrait to respect a portrait orientation activity, after
* insets such as the status and nav bars are applied, the activity may actually have a
@@ -7688,6 +7726,7 @@
private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig,
int windowingMode) {
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
+ mIsEligibleForFixedOrientationLetterbox = false;
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
final Rect stableBounds = new Rect();
// If orientation is respected when insets are applied, then stableBounds will be empty.
@@ -7727,8 +7766,11 @@
// make it fit the available bounds by scaling down its bounds.
final int forcedOrientation = getRequestedConfigurationOrientation();
- if (forcedOrientation == ORIENTATION_UNDEFINED
- || (forcedOrientation == parentOrientation && orientationRespectedWithInsets)) {
+ mIsEligibleForFixedOrientationLetterbox = forcedOrientation != ORIENTATION_UNDEFINED
+ && forcedOrientation != parentOrientation;
+
+ if (!mIsEligibleForFixedOrientationLetterbox && (forcedOrientation == ORIENTATION_UNDEFINED
+ || orientationRespectedWithInsets)) {
return;
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index d5abe4f..56adcfd 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -93,7 +93,6 @@
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
-import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -101,6 +100,7 @@
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
import android.os.Binder;
import android.os.Debug;
@@ -539,8 +539,8 @@
* animation.
*/
HardwareBuffer createCrossProfileAppsThumbnail(
- @DrawableRes int thumbnailDrawableRes, Rect frame) {
- return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
+ Drawable thumbnailDrawable, Rect frame) {
+ return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawable, frame);
}
Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4009220..c87027d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5486,26 +5486,46 @@
}
void updateKeepClearAreas() {
+ final List<Rect> restrictedKeepClearAreas = new ArrayList();
+ final List<Rect> unrestrictedKeepClearAreas = new ArrayList();
+ getKeepClearAreas(restrictedKeepClearAreas, unrestrictedKeepClearAreas);
mWmService.mDisplayNotificationController.dispatchKeepClearAreasChanged(
- this, getKeepClearAreas());
+ this, restrictedKeepClearAreas, unrestrictedKeepClearAreas);
}
/**
- * Returns all keep-clear areas from visible windows on this display.
+ * Fills {@param outRestricted} with all keep-clear areas from visible, relevant windows
+ * on this display, which set restricted keep-clear areas.
+ * Fills {@param outUnrestricted} with keep-clear areas from visible, relevant windows on this
+ * display, which set unrestricted keep-clear areas.
+ *
+ * For context on restricted vs unrestricted keep-clear areas, see
+ * {@link android.Manifest.permission.USE_UNRESTRICTED_KEEP_CLEAR_AREAS}.
*/
- ArrayList<Rect> getKeepClearAreas() {
- final ArrayList<Rect> keepClearAreas = new ArrayList<Rect>();
+ void getKeepClearAreas(List<Rect> outRestricted, List<Rect> outUnrestricted) {
final Matrix tmpMatrix = new Matrix();
final float[] tmpFloat9 = new float[9];
forAllWindows(w -> {
if (w.isVisible() && !w.inPinnedWindowingMode()) {
- keepClearAreas.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
+ if (w.mSession.mSetsUnrestrictedKeepClearAreas) {
+ outUnrestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
+ } else {
+ outRestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
+ }
}
// We stop traversing when we reach the base of a fullscreen app.
return w.getWindowType() == TYPE_BASE_APPLICATION
&& w.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
}, true);
+ }
+
+ /**
+ * Returns all keep-clear areas from visible, relevant windows on this display.
+ */
+ ArrayList<Rect> getKeepClearAreas() {
+ final ArrayList<Rect> keepClearAreas = new ArrayList<Rect>();
+ getKeepClearAreas(keepClearAreas, keepClearAreas);
return keepClearAreas;
}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
index 276dbe9..e18d539 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
@@ -120,12 +120,13 @@
mDisplayListeners.finishBroadcast();
}
- void dispatchKeepClearAreasChanged(DisplayContent display, List<Rect> keepClearAreas) {
+ void dispatchKeepClearAreasChanged(DisplayContent display, List<Rect> restricted,
+ List<Rect> unrestricted) {
int count = mDisplayListeners.beginBroadcast();
for (int i = 0; i < count; ++i) {
try {
mDisplayListeners.getBroadcastItem(i).onKeepClearAreasChanged(
- display.mDisplayId, keepClearAreas);
+ display.mDisplayId, restricted, unrestricted);
} catch (RemoteException e) {
}
}
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index f91969b..1f0fdcf 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -24,6 +24,7 @@
import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.PointF;
import android.os.Debug;
import android.os.IBinder;
@@ -254,6 +255,25 @@
}
}
+ @Override
+ @Nullable
+ public SurfaceControl createSurfaceForGestureMonitor(String name, int displayId) {
+ synchronized (mService.mGlobalLock) {
+ final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+ if (dc == null) {
+ Slog.e(TAG, "Failed to create a gesture monitor on display: " + displayId
+ + " - DisplayContent not found.");
+ return null;
+ }
+ return mService.makeSurfaceBuilder(dc.getSession())
+ .setContainerLayer()
+ .setName(name)
+ .setCallsite("createSurfaceForGestureMonitor")
+ .setParent(dc.getSurfaceControl())
+ .build();
+ }
+ }
+
/** Waits until the built-in input devices have been configured. */
public boolean waitForInputDevicesReady(long timeoutMillis) {
synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 10776ab..1e12173 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -32,6 +32,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
import android.app.StatusBarManager;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -134,7 +135,7 @@
/** Updates the target which can control system bars. */
void updateBarControlTarget(@Nullable WindowState focusedWin) {
- if (mFocusedWin != focusedWin){
+ if (mFocusedWin != focusedWin) {
abortTransient();
}
mFocusedWin = focusedWin;
@@ -156,7 +157,7 @@
}
boolean isHidden(@InternalInsetsType int type) {
- final InsetsSourceProvider provider = mStateController.peekSourceProvider(type);
+ final InsetsSourceProvider provider = mStateController.peekSourceProvider(type);
return provider != null && provider.hasWindow() && !provider.getSource().isVisible();
}
@@ -181,6 +182,10 @@
mShowingTransientTypes.toArray(), isGestureOnSystemBar);
}
updateBarControlTarget(mFocusedWin);
+ dispatchTransientSystemBarsVisibilityChanged(
+ mFocusedWin,
+ isTransient(ITYPE_STATUS_BAR) || isTransient(ITYPE_NAVIGATION_BAR),
+ isGestureOnSystemBar);
// The leashes can be created while updating bar control target. The surface transaction
// of the new leashes might not be applied yet. The callback posted here ensures we can
@@ -198,6 +203,12 @@
if (mShowingTransientTypes.size() == 0) {
return;
}
+
+ dispatchTransientSystemBarsVisibilityChanged(
+ mFocusedWin,
+ /* areVisible= */ false,
+ /* wereRevealedFromSwipeOnSystemBar= */ false);
+
startAnimation(false /* show */, () -> {
synchronized (mDisplayContent.mWmService.mGlobalLock) {
for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) {
@@ -373,6 +384,11 @@
mDisplayContent.getDisplayId(), mShowingTransientTypes.toArray());
}
mShowingTransientTypes.clear();
+
+ dispatchTransientSystemBarsVisibilityChanged(
+ mFocusedWin,
+ /* areVisible= */ false,
+ /* wereRevealedFromSwipeOnSystemBar= */ false);
}
private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin,
@@ -521,6 +537,32 @@
listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show);
}
+ private void dispatchTransientSystemBarsVisibilityChanged(
+ @Nullable WindowState focusedWindow,
+ boolean areVisible,
+ boolean wereRevealedFromSwipeOnSystemBar) {
+ if (focusedWindow == null) {
+ return;
+ }
+
+ Task task = focusedWindow.getTask();
+ if (task == null) {
+ return;
+ }
+
+ int taskId = task.mTaskId;
+ boolean isValidTaskId = taskId != ActivityTaskManager.INVALID_TASK_ID;
+ if (!isValidTaskId) {
+ return;
+ }
+
+ mDisplayContent.mWmService.mTaskSystemBarsListenerController
+ .dispatchTransientSystemBarVisibilityChanged(
+ taskId,
+ areVisible,
+ wereRevealedFromSwipeOnSystemBar);
+ }
+
private class BarWindow {
private final int mId;
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 1955e30..ad2767c 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -126,6 +126,9 @@
@LetterboxReachabilityPosition
private volatile int mLetterboxPositionForReachability;
+ // Whether education is allowed for letterboxed fullscreen apps.
+ private boolean mIsEducationEnabled;
+
LetterboxConfiguration(Context systemUiContext) {
mContext = systemUiContext;
mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
@@ -143,6 +146,8 @@
R.bool.config_letterboxIsReachabilityEnabled);
mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext);
mLetterboxPositionForReachability = mDefaultPositionForReachability;
+ mIsEducationEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsEducationEnabled);
}
/**
@@ -501,4 +506,27 @@
mLetterboxPositionForReachability = Math.max(mLetterboxPositionForReachability - 1, 0);
}
+ /**
+ * Whether education is allowed for letterboxed fullscreen apps.
+ */
+ boolean getIsEducationEnabled() {
+ return mIsEducationEnabled;
+ }
+
+ /**
+ * Overrides whether education is allowed for letterboxed fullscreen apps.
+ */
+ void setIsEducationEnabled(boolean enabled) {
+ mIsEducationEnabled = enabled;
+ }
+
+ /**
+ * Resets whether education is allowed for letterboxed fullscreen apps to
+ * {@link R.bool.config_letterboxIsEducationEnabled}.
+ */
+ void resetIsEducationEnabled() {
+ mIsEducationEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsEducationEnabled);
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 76a7981..ee03d02 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -981,6 +981,29 @@
mWmService.checkDrawnWindowsLocked();
}
+ final int N = mWmService.mPendingRemove.size();
+ if (N > 0) {
+ if (mWmService.mPendingRemoveTmp.length < N) {
+ mWmService.mPendingRemoveTmp = new WindowState[N + 10];
+ }
+ mWmService.mPendingRemove.toArray(mWmService.mPendingRemoveTmp);
+ mWmService.mPendingRemove.clear();
+ ArrayList<DisplayContent> displayList = new ArrayList();
+ for (i = 0; i < N; i++) {
+ final WindowState w = mWmService.mPendingRemoveTmp[i];
+ w.removeImmediately();
+ final DisplayContent displayContent = w.getDisplayContent();
+ if (displayContent != null && !displayList.contains(displayContent)) {
+ displayList.add(displayContent);
+ }
+ }
+
+ for (int j = displayList.size() - 1; j >= 0; --j) {
+ final DisplayContent dc = displayList.get(j);
+ dc.assignWindowLayers(true /*setLayoutNeeded*/);
+ }
+ }
+
forAllDisplays(dc -> {
dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
dc.updateSystemGestureExclusion();
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 98acc46..2ae2b43 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
@@ -114,6 +115,7 @@
private String mRelayoutTag;
private final InsetsVisibilities mDummyRequestedVisibilities = new InsetsVisibilities();
private final InsetsSourceControl[] mDummyControls = new InsetsSourceControl[0];
+ final boolean mSetsUnrestrictedKeepClearAreas;
public Session(WindowManagerService service, IWindowSessionCallback callback) {
mService = service;
@@ -132,6 +134,9 @@
== PERMISSION_GRANTED;
mCanStartTasksFromRecents = service.mContext.checkCallingOrSelfPermission(
START_TASKS_FROM_RECENTS) == PERMISSION_GRANTED;
+ mSetsUnrestrictedKeepClearAreas =
+ service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_KEEP_CLEAR_AREAS)
+ == PERMISSION_GRANTED;
mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
mDragDropController = mService.mDragDropController;
StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7d06526..97735a2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3434,6 +3434,9 @@
// Whether the direct top activity is in size compat mode on foreground.
info.topActivityInSizeCompat = isTopActivityResumed
&& mReuseActivitiesReport.top.inSizeCompatMode();
+ // Whether the direct top activity is eligible for letterbox education.
+ info.topActivityEligibleForLetterboxEducation = isTopActivityResumed
+ && mReuseActivitiesReport.top.isEligibleForLetterboxEducation();
// Whether the direct top activity requested showing camera compat control.
info.cameraCompatControlState = isTopActivityResumed
? mReuseActivitiesReport.top.getCameraCompatControlState()
diff --git a/services/core/java/com/android/server/wm/TaskSystemBarsListenerController.java b/services/core/java/com/android/server/wm/TaskSystemBarsListenerController.java
new file mode 100644
index 0000000..acb6061
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskSystemBarsListenerController.java
@@ -0,0 +1,63 @@
+/*
+ * 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.wm;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
+
+import java.util.HashSet;
+import java.util.concurrent.Executor;
+
+/**
+ * Manages dispatch of task system bar changes to interested listeners. All invocations must be
+ * performed while the {@link WindowManagerService#getWindowManagerLock() Window Manager Lock} is
+ * held.
+ */
+final class TaskSystemBarsListenerController {
+
+ private final HashSet<TaskSystemBarsListener> mListeners = new HashSet<>();
+ private final Executor mBackgroundExecutor;
+
+ TaskSystemBarsListenerController() {
+ this.mBackgroundExecutor = BackgroundThread.getExecutor();
+ }
+
+ void registerListener(TaskSystemBarsListener listener) {
+ mListeners.add(listener);
+ }
+
+ void unregisterListener(TaskSystemBarsListener listener) {
+ mListeners.remove(listener);
+ }
+
+ void dispatchTransientSystemBarVisibilityChanged(
+ int taskId,
+ boolean visible,
+ boolean wereRevealedFromSwipeOnSystemBar) {
+ HashSet<TaskSystemBarsListener> localListeners;
+ localListeners = new HashSet<>(mListeners);
+
+ mBackgroundExecutor.execute(() -> {
+ for (TaskSystemBarsListener listener : localListeners) {
+ listener.onTransientSystemBarsVisibilityChanged(
+ taskId,
+ visible,
+ wereRevealedFromSwipeOnSystemBar);
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 20fa7a9..4900f929 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -256,6 +256,25 @@
}
/**
+ * An interface to be notified when the system bars for a task change.
+ */
+ public interface TaskSystemBarsListener {
+
+ /**
+ * Called when the visibility of the system bars of a task change.
+ *
+ * @param taskId the identifier of the task.
+ * @param visible if the transient system bars are visible.
+ * @param wereRevealedFromSwipeOnSystemBar if the transient bars were revealed due to a
+ * swipe gesture on a system bar.
+ */
+ void onTransientSystemBarsVisibilityChanged(
+ int taskId,
+ boolean visible,
+ boolean wereRevealedFromSwipeOnSystemBar);
+ }
+
+ /**
* An interface to be notified when keyguard exit animation should start.
*/
public interface KeyguardExitAnimationStartListener {
@@ -519,6 +538,20 @@
public abstract void registerAppTransitionListener(AppTransitionListener listener);
/**
+ * Registers a listener to be notified to when the system bars of a task changes.
+ *
+ * @param listener The listener to register.
+ */
+ public abstract void registerTaskSystemBarsListener(TaskSystemBarsListener listener);
+
+ /**
+ * Registers a listener to be notified to when the system bars of a task changes.
+ *
+ * @param listener The listener to unregister.
+ */
+ public abstract void unregisterTaskSystemBarsListener(TaskSystemBarsListener listener);
+
+ /**
* Registers a listener to be notified to start the keyguard exit animation.
*
* @param listener The listener to register.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b37cb4f..1167cb5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -222,6 +222,7 @@
import android.util.EventLog;
import android.util.MergedConfiguration;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import android.util.TypedValue;
@@ -586,6 +587,20 @@
final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
/**
+ * Windows whose animations have ended and now must be removed.
+ */
+ final ArrayList<WindowState> mPendingRemove = new ArrayList<>();
+
+ /**
+ * Used when processing mPendingRemove to avoid working on the original array.
+ */
+ WindowState[] mPendingRemoveTmp = new WindowState[20];
+
+ // TODO: use WindowProcessController once go/wm-unified is done.
+ /** Mapping of process pids to configurations */
+ final SparseArray<Configuration> mProcessConfigurations = new SparseArray<>();
+
+ /**
* Mapping of displayId to {@link DisplayImePolicy}.
* Note that this can be accessed without holding the lock.
*/
@@ -683,6 +698,7 @@
() -> mDisplayRotationController = null;
final DisplayWindowListenerController mDisplayNotificationController;
+ final TaskSystemBarsListenerController mTaskSystemBarsListenerController;
boolean mDisplayFrozen = false;
long mDisplayFreezeTime = 0;
@@ -1265,6 +1281,7 @@
mScreenFrozenLock.setReferenceCounted(false);
mDisplayNotificationController = new DisplayWindowListenerController(this);
+ mTaskSystemBarsListenerController = new TaskSystemBarsListenerController();
mActivityManager = ActivityManager.getService();
mActivityTaskManager = ActivityTaskManager.getService();
@@ -2036,6 +2053,7 @@
dc.mWinRemovedSinceNullFocus.add(win);
}
mEmbeddedWindowController.onWindowRemoved(win);
+ mPendingRemove.remove(win);
mResizingWindows.remove(win);
updateNonSystemOverlayWindowsVisibilityIfNeeded(win, false /* surfaceShown */);
mWindowsChanged = true;
@@ -6364,6 +6382,23 @@
}
}
}
+ if (mPendingRemove.size() > 0) {
+ pw.println();
+ pw.println(" Remove pending for:");
+ for (int i=mPendingRemove.size()-1; i>=0; i--) {
+ WindowState w = mPendingRemove.get(i);
+ if (windows == null || windows.contains(w)) {
+ pw.print(" Remove #"); pw.print(i); pw.print(' ');
+ pw.print(w);
+ if (dumpAll) {
+ pw.println(":");
+ w.dump(pw, " ", true);
+ } else {
+ pw.println();
+ }
+ }
+ }
+ }
if (mForceRemoves != null && mForceRemoves.size() > 0) {
pw.println();
pw.println(" Windows force removing:");
@@ -7591,6 +7626,20 @@
}
@Override
+ public void registerTaskSystemBarsListener(TaskSystemBarsListener listener) {
+ synchronized (mGlobalLock) {
+ mTaskSystemBarsListenerController.registerListener(listener);
+ }
+ }
+
+ @Override
+ public void unregisterTaskSystemBarsListener(TaskSystemBarsListener listener) {
+ synchronized (mGlobalLock) {
+ mTaskSystemBarsListenerController.unregisterListener(listener);
+ }
+ }
+
+ @Override
public void registerKeyguardExitAnimationStartListener(
KeyguardExitAnimationStartListener listener) {
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 0f8587c..1cf4c1b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -822,6 +822,29 @@
return 0;
}
+ private int runSetLetterboxIsEducationEnabled(PrintWriter pw) throws RemoteException {
+ String arg = getNextArg();
+ final boolean enabled;
+ switch (arg) {
+ case "true":
+ case "1":
+ enabled = true;
+ break;
+ case "false":
+ case "0":
+ enabled = false;
+ break;
+ default:
+ getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
+ return -1;
+ }
+
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setIsEducationEnabled(enabled);
+ }
+ return 0;
+ }
+
private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
if (peekNextArg() == null) {
getErrPrintWriter().println("Error: No arguments provided.");
@@ -859,6 +882,9 @@
case "--defaultPositionForReachability":
runSetLetterboxDefaultPositionForReachability(pw);
break;
+ case "--isEducationEnabled":
+ runSetLetterboxIsEducationEnabled(pw);
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -903,6 +929,9 @@
case "defaultPositionForReachability":
mLetterboxConfiguration.getDefaultPositionForReachability();
break;
+ case "isEducationEnabled":
+ mLetterboxConfiguration.getIsEducationEnabled();
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -998,6 +1027,7 @@
mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
mLetterboxConfiguration.resetIsReachabilityEnabled();
mLetterboxConfiguration.resetDefaultPositionForReachability();
+ mLetterboxConfiguration.resetIsEducationEnabled();
}
}
@@ -1014,6 +1044,8 @@
pw.println("Default position for reachability: "
+ LetterboxConfiguration.letterboxReachabilityPositionToString(
mLetterboxConfiguration.getDefaultPositionForReachability()));
+ pw.println("Is education enabled: "
+ + mLetterboxConfiguration.getIsEducationEnabled());
pw.println("Background type: "
+ LetterboxConfiguration.letterboxBackgroundTypeToString(
@@ -1154,10 +1186,12 @@
pw.println(" --defaultPositionForReachability [left|center|right]");
pw.println(" Default horizontal position of app window when reachability is.");
pw.println(" enabled.");
+ pw.println(" --isEducationEnabled [true|1|false|0]");
+ pw.println(" Whether education is allowed for letterboxed fullscreen apps.");
pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
pw.println(" |horizontalPositionMultiplier|isReachabilityEnabled");
- pw.println(" |defaultPositionMultiplierForReachability]");
+ pw.println(" isEducationEnabled||defaultPositionMultiplierForReachability]");
pw.println(" Resets overrides to default values for specified properties separated");
pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
pw.println(" If no arguments provided, all values will be reset.");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0a02b44..79c64b1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4887,20 +4887,15 @@
if (hasSurface) {
mWmService.mDestroySurface.add(this);
}
+ if (mRemoveOnExit) {
+ mWmService.mPendingRemove.add(this);
+ mRemoveOnExit = false;
+ }
}
mAnimatingExit = false;
getDisplayContent().mWallpaperController.hideWallpapers(this);
}
- @Override
- boolean handleCompleteDeferredRemoval() {
- if (mRemoveOnExit) {
- mRemoveOnExit = false;
- removeImmediately();
- }
- return super.handleCompleteDeferredRemoval();
- }
-
boolean clearAnimatingFlags() {
boolean didSomething = false;
// We don't want to clear it out for windows that get replaced, because the
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 95ef5f7..99abf44 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -103,6 +103,7 @@
"libappfuse",
"libbinder_ndk",
"libbinder",
+ "libchrome",
"libcutils",
"libcrypto",
"liblog",
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index 43018a9..adc91fc 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -186,7 +186,7 @@
};
/** Creates a new uinput device and assigns a file descriptor. */
-static int openUinput(const char* readableName, jint vendorId, jint productId,
+static int openUinput(const char* readableName, jint vendorId, jint productId, const char* phys,
DeviceType deviceType, jint screenHeight, jint screenWidth) {
android::base::unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK)));
if (fd < 0) {
@@ -194,6 +194,8 @@
return -errno;
}
+ ioctl(fd, UI_SET_PHYS, phys);
+
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_EVBIT, EV_SYN);
switch (deviceType) {
@@ -295,28 +297,30 @@
return fd.release();
}
-static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId,
+static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId, jstring phys,
DeviceType deviceType, int screenHeight, int screenWidth) {
ScopedUtfChars readableName(env, name);
- return openUinput(readableName.c_str(), vendorId, productId, deviceType, screenHeight,
- screenWidth);
+ ScopedUtfChars readablePhys(env, phys);
+ return openUinput(readableName.c_str(), vendorId, productId, readablePhys.c_str(), deviceType,
+ screenHeight, screenWidth);
}
static int nativeOpenUinputKeyboard(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
- jint productId) {
- return openUinputJni(env, name, vendorId, productId, DeviceType::KEYBOARD, /* screenHeight */ 0,
- /* screenWidth */ 0);
+ jint productId, jstring phys) {
+ return openUinputJni(env, name, vendorId, productId, phys, DeviceType::KEYBOARD,
+ /* screenHeight */ 0, /* screenWidth */ 0);
}
static int nativeOpenUinputMouse(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
- jint productId) {
- return openUinputJni(env, name, vendorId, productId, DeviceType::MOUSE, /* screenHeight */ 0,
- /* screenWidth */ 0);
+ jint productId, jstring phys) {
+ return openUinputJni(env, name, vendorId, productId, phys, DeviceType::MOUSE,
+ /* screenHeight */ 0, /* screenWidth */ 0);
}
static int nativeOpenUinputTouchscreen(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
- jint productId, jint height, jint width) {
- return openUinputJni(env, name, vendorId, productId, DeviceType::TOUCHSCREEN, height, width);
+ jint productId, jstring phys, jint height, jint width) {
+ return openUinputJni(env, name, vendorId, productId, phys, DeviceType::TOUCHSCREEN, height,
+ width);
}
static bool nativeCloseUinput(JNIEnv* env, jobject thiz, jint fd) {
@@ -435,9 +439,11 @@
}
static JNINativeMethod methods[] = {
- {"nativeOpenUinputKeyboard", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputKeyboard},
- {"nativeOpenUinputMouse", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputMouse},
- {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IIII)I",
+ {"nativeOpenUinputKeyboard", "(Ljava/lang/String;IILjava/lang/String;)I",
+ (void*)nativeOpenUinputKeyboard},
+ {"nativeOpenUinputMouse", "(Ljava/lang/String;IILjava/lang/String;)I",
+ (void*)nativeOpenUinputMouse},
+ {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)I",
(void*)nativeOpenUinputTouchscreen},
{"nativeCloseUinput", "(I)Z", (void*)nativeCloseUinput},
{"nativeWriteKeyEvent", "(III)Z", (void*)nativeWriteKeyEvent},
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index df5fb28..8cb27e1 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -457,6 +457,9 @@
mInputManager->getReader().dump(dump);
dump += "\n";
+ mInputManager->getUnwantedInteractionBlocker().dump(dump);
+ dump += "\n";
+
mInputManager->getClassifier().dump(dump);
dump += "\n";
@@ -704,6 +707,7 @@
void NativeInputManager::notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) {
ATRACE_CALL();
+ mInputManager->getUnwantedInteractionBlocker().notifyInputDevicesChanged(inputDevices);
JNIEnv* env = jniEnv();
size_t count = inputDevices.size();
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 0da8f7e..166a0f5 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -244,6 +244,9 @@
return hasLatLong(location.v1_0);
}
+bool isSvStatusRegistered = false;
+bool isNmeaRegistered = false;
+
} // namespace
static inline jboolean boolToJbool(bool value) {
@@ -505,6 +508,13 @@
template <class T_list, class T_sv_info>
Return<void> GnssCallback::gnssSvStatusCbImpl(const T_list& svStatus) {
+ // In HIDL or AIDL v1, if no listener is registered, do not report svInfoList to the framework.
+ if (gnssHalAidl == nullptr || gnssHalAidl->getInterfaceVersion() == 1) {
+ if (!isSvStatusRegistered) {
+ return Void();
+ }
+ }
+
JNIEnv* env = getJniEnv();
uint32_t listSize = getGnssSvInfoListSize(svStatus);
@@ -566,8 +576,12 @@
return Void();
}
-Return<void> GnssCallback::gnssNmeaCb(
- int64_t timestamp, const ::android::hardware::hidl_string& nmea) {
+Return<void> GnssCallback::gnssNmeaCb(int64_t timestamp,
+ const ::android::hardware::hidl_string& nmea) {
+ // In HIDL, if no listener is registered, do not report nmea to the framework.
+ if (!isNmeaRegistered) {
+ return Void();
+ }
JNIEnv* env = getJniEnv();
/*
* The Java code will call back to read these values.
@@ -680,6 +694,12 @@
}
Status GnssCallbackAidl::gnssNmeaCb(const int64_t timestamp, const std::string& nmea) {
+ // In AIDL v1, if no listener is registered, do not report nmea to the framework.
+ if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() == 1) {
+ if (!isNmeaRegistered) {
+ return Status::ok();
+ }
+ }
JNIEnv* env = getJniEnv();
/*
* The Java code will call back to read these values.
@@ -1504,11 +1524,14 @@
JNIEnv* /* env */, jclass, jint mode, jint recurrence, jint min_interval,
jint preferred_accuracy, jint preferred_time, jboolean low_power_mode) {
if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
- auto status = gnssHalAidl->setPositionMode(static_cast<IGnssAidl::GnssPositionMode>(mode),
- static_cast<IGnssAidl::GnssPositionRecurrence>(
- recurrence),
- min_interval, preferred_accuracy, preferred_time,
- low_power_mode);
+ IGnssAidl::PositionModeOptions options;
+ options.mode = static_cast<IGnssAidl::GnssPositionMode>(mode);
+ options.recurrence = static_cast<IGnssAidl::GnssPositionRecurrence>(recurrence);
+ options.minIntervalMs = min_interval;
+ options.preferredAccuracyMeters = preferred_accuracy;
+ options.preferredTimeMs = preferred_time;
+ options.lowPowerMode = low_power_mode;
+ auto status = gnssHalAidl->setPositionMode(options);
return checkAidlStatus(status, "IGnssAidl setPositionMode() failed.");
}
@@ -1559,6 +1582,58 @@
return checkHidlReturn(result, "IGnss stop() failed.");
}
+static jboolean android_location_gnss_hal_GnssNative_start_sv_status_collection(JNIEnv* /* env */,
+ jclass) {
+ isSvStatusRegistered = true;
+ if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+ auto status = gnssHalAidl->startSvStatus();
+ return checkAidlStatus(status, "IGnssAidl startSvStatus() failed.");
+ }
+ if (gnssHal == nullptr) {
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
+static jboolean android_location_gnss_hal_GnssNative_stop_sv_status_collection(JNIEnv* /* env */,
+ jclass) {
+ isSvStatusRegistered = false;
+ if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+ auto status = gnssHalAidl->stopSvStatus();
+ return checkAidlStatus(status, "IGnssAidl stopSvStatus() failed.");
+ }
+ if (gnssHal == nullptr) {
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
+static jboolean android_location_gnss_hal_GnssNative_start_nmea_message_collection(
+ JNIEnv* /* env */, jclass) {
+ isNmeaRegistered = true;
+ if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+ auto status = gnssHalAidl->startNmea();
+ return checkAidlStatus(status, "IGnssAidl startNmea() failed.");
+ }
+ if (gnssHal == nullptr) {
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
+static jboolean android_location_gnss_hal_GnssNative_stop_nmea_message_collection(JNIEnv* /* env */,
+ jclass) {
+ isNmeaRegistered = false;
+ if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+ auto status = gnssHalAidl->stopNmea();
+ return checkAidlStatus(status, "IGnssAidl stopNmea() failed.");
+ }
+ if (gnssHal == nullptr) {
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
static void android_location_gnss_hal_GnssNative_delete_aiding_data(JNIEnv* /* env */, jclass,
jint flags) {
if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
@@ -1986,7 +2061,7 @@
jfloat eplMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEpl);
jfloat eplUncMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEplUnc);
uint16_t corrFlags = static_cast<uint16_t>(correctionFlags);
- jobject reflectingPlaneObj;
+ jobject reflectingPlaneObj = nullptr;
bool has_ref_plane = (corrFlags & GnssSingleSatCorrectionFlags::HAS_REFLECTING_PLANE) != 0;
if (has_ref_plane) {
reflectingPlaneObj =
@@ -2010,6 +2085,7 @@
.azimuthDegrees = azimuthDegreeRefPlane,
};
}
+ env->DeleteLocalRef(reflectingPlaneObj);
SingleSatCorrection_V1_0 singleSatCorrection = {
.singleSatCorrectionFlags = corrFlags,
@@ -2041,6 +2117,7 @@
};
list[i] = singleSatCorrection_1_1;
+ env->DeleteLocalRef(singleSatCorrectionObj);
}
}
@@ -2058,6 +2135,7 @@
singleSatCorrection.constellation = static_cast<GnssConstellationType_V1_0>(constType),
list[i] = singleSatCorrection;
+ env->DeleteLocalRef(singleSatCorrectionObj);
}
}
@@ -2128,6 +2206,7 @@
hidl_vec<SingleSatCorrection_V1_0> list(len);
getSingleSatCorrectionList_1_0(env, singleSatCorrectionList, list);
+ env->DeleteLocalRef(singleSatCorrectionList);
measurementCorrections_1_0.satCorrections = list;
auto result = gnssCorrectionsIface_V1_0->setCorrections(measurementCorrections_1_0);
@@ -2362,6 +2441,16 @@
{"native_is_gnss_visibility_control_supported", "()Z",
reinterpret_cast<void*>(
android_location_gnss_hal_GnssNative_is_gnss_visibility_control_supported)},
+ {"native_start_sv_status_collection", "()Z",
+ reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_start_sv_status_collection)},
+ {"native_stop_sv_status_collection", "()Z",
+ reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_stop_sv_status_collection)},
+ {"native_start_nmea_message_collection", "()Z",
+ reinterpret_cast<void*>(
+ android_location_gnss_hal_GnssNative_start_nmea_message_collection)},
+ {"native_stop_nmea_message_collection", "()Z",
+ reinterpret_cast<void*>(
+ android_location_gnss_hal_GnssNative_stop_nmea_message_collection)},
};
static const JNINativeMethod sBatchingMethods[] = {
diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp
index d760b4d..424ffd4 100644
--- a/services/core/jni/gnss/AGnssRil.cpp
+++ b/services/core/jni/gnss/AGnssRil.cpp
@@ -41,7 +41,7 @@
jboolean AGnssRil::setSetId(jint type, const jstring& setid_string) {
JNIEnv* env = getJniEnv();
ScopedJniString jniSetId{env, setid_string};
- auto status = mIAGnssRil->setSetId((IAGnssRil::SetIDType)type, jniSetId.c_str());
+ auto status = mIAGnssRil->setSetId((IAGnssRil::SetIdType)type, jniSetId.c_str());
return checkAidlStatus(status, "IAGnssRilAidl setSetId() failed.");
}
diff --git a/services/core/jni/gnss/GnssAntennaInfoCallback.cpp b/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
index fbc000b..99d06eb 100644
--- a/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
+++ b/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
@@ -226,17 +226,18 @@
env->NewObject(class_gnssAntennaInfoBuilder, method_gnssAntennaInfoBuilderCtor);
// Set fields
- env->CallObjectMethod(gnssAntennaInfoBuilderObject,
- method_gnssAntennaInfoBuilderSetCarrierFrequencyMHz,
- gnssAntennaInfo.carrierFrequencyMHz);
- env->CallObjectMethod(gnssAntennaInfoBuilderObject,
- method_gnssAntennaInfoBuilderSetPhaseCenterOffset, phaseCenterOffset);
- env->CallObjectMethod(gnssAntennaInfoBuilderObject,
- method_gnssAntennaInfoBuilderSetPhaseCenterVariationCorrections,
- phaseCenterVariationCorrections);
- env->CallObjectMethod(gnssAntennaInfoBuilderObject,
- method_gnssAntennaInfoBuilderSetSignalGainCorrections,
- signalGainCorrections);
+ callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+ method_gnssAntennaInfoBuilderSetCarrierFrequencyMHz,
+ gnssAntennaInfo.carrierFrequencyMHz);
+ callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+ method_gnssAntennaInfoBuilderSetPhaseCenterOffset,
+ phaseCenterOffset);
+ callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+ method_gnssAntennaInfoBuilderSetPhaseCenterVariationCorrections,
+ phaseCenterVariationCorrections);
+ callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+ method_gnssAntennaInfoBuilderSetSignalGainCorrections,
+ signalGainCorrections);
// build
jobject gnssAntennaInfoObject =
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index fbdeec6..34ca559 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -212,13 +212,14 @@
jobject gnssMeasurementsEventBuilderObject =
env->NewObject(class_gnssMeasurementsEventBuilder,
method_gnssMeasurementsEventBuilderCtor);
- env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
- method_gnssMeasurementsEventBuilderSetClock, clock);
- env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
- method_gnssMeasurementsEventBuilderSetMeasurements, measurementArray);
- env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
- method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
- gnssAgcArray);
+ callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+ method_gnssMeasurementsEventBuilderSetClock, clock);
+ callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+ method_gnssMeasurementsEventBuilderSetMeasurements,
+ measurementArray);
+ callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+ method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
+ gnssAgcArray);
jobject gnssMeasurementsEventObject =
env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
method_gnssMeasurementsEventBuilderBuild);
@@ -359,9 +360,7 @@
jobjectArray measurementArray = translateAllGnssMeasurements(env, data.measurements);
jobjectArray gnssAgcArray = nullptr;
- if (data.gnssAgcs.has_value()) {
- gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs.value());
- }
+ gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs);
setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray);
env->DeleteLocalRef(clock);
@@ -410,24 +409,24 @@
satellitePvt.satClockInfo.satHardwareCodeBiasMeters,
satellitePvt.satClockInfo.satTimeCorrectionMeters,
satellitePvt.satClockInfo.satClkDriftMps);
- env->CallObjectMethod(satellitePvtBuilderObject,
- method_satellitePvtBuilderSetPositionEcef, positionEcef);
- env->CallObjectMethod(satellitePvtBuilderObject,
- method_satellitePvtBuilderSetVelocityEcef, velocityEcef);
- env->CallObjectMethod(satellitePvtBuilderObject, method_satellitePvtBuilderSetClockInfo,
- clockInfo);
+ callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+ method_satellitePvtBuilderSetPositionEcef, positionEcef);
+ callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+ method_satellitePvtBuilderSetVelocityEcef, velocityEcef);
+ callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+ method_satellitePvtBuilderSetClockInfo, clockInfo);
}
if (satFlags & SatellitePvt::HAS_IONO) {
- env->CallObjectMethod(satellitePvtBuilderObject,
- method_satellitePvtBuilderSetIonoDelayMeters,
- satellitePvt.ionoDelayMeters);
+ callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+ method_satellitePvtBuilderSetIonoDelayMeters,
+ satellitePvt.ionoDelayMeters);
}
if (satFlags & SatellitePvt::HAS_TROPO) {
- env->CallObjectMethod(satellitePvtBuilderObject,
- method_satellitePvtBuilderSetTropoDelayMeters,
- satellitePvt.tropoDelayMeters);
+ callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+ method_satellitePvtBuilderSetTropoDelayMeters,
+ satellitePvt.tropoDelayMeters);
}
jobject satellitePvtObject =
@@ -455,17 +454,19 @@
jobject correlationVectorBuilderObject =
env->NewObject(class_correlationVectorBuilder,
method_correlationVectorBuilderCtor);
- env->CallObjectMethod(correlationVectorBuilderObject,
- method_correlationVectorBuilderSetMagnitude, magnitudeArray);
- env->CallObjectMethod(correlationVectorBuilderObject,
- method_correlationVectorBuilderSetFrequencyOffsetMetersPerSecond,
- correlationVector.frequencyOffsetMps);
- env->CallObjectMethod(correlationVectorBuilderObject,
- method_correlationVectorBuilderSetSamplingStartMeters,
- correlationVector.samplingStartM);
- env->CallObjectMethod(correlationVectorBuilderObject,
- method_correlationVectorBuilderSetSamplingWidthMeters,
- correlationVector.samplingWidthM);
+ callObjectMethodIgnoringResult(env, correlationVectorBuilderObject,
+ method_correlationVectorBuilderSetMagnitude,
+ magnitudeArray);
+ callObjectMethodIgnoringResult(
+ env, correlationVectorBuilderObject,
+ method_correlationVectorBuilderSetFrequencyOffsetMetersPerSecond,
+ correlationVector.frequencyOffsetMps);
+ callObjectMethodIgnoringResult(env, correlationVectorBuilderObject,
+ method_correlationVectorBuilderSetSamplingStartMeters,
+ correlationVector.samplingStartM);
+ callObjectMethodIgnoringResult(env, correlationVectorBuilderObject,
+ method_correlationVectorBuilderSetSamplingWidthMeters,
+ correlationVector.samplingWidthM);
jobject correlationVectorObject =
env->CallObjectMethod(correlationVectorBuilderObject,
method_correlationVectorBuilderBuild);
@@ -508,8 +509,8 @@
return gnssMeasurementArray;
}
-jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs(
- JNIEnv* env, const std::vector<std::optional<GnssAgc>>& agcs) {
+jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs(JNIEnv* env,
+ const std::vector<GnssAgc>& agcs) {
if (agcs.size() == 0) {
return nullptr;
}
@@ -518,18 +519,17 @@
env->NewObjectArray(agcs.size(), class_gnssAgc, nullptr /* initialElement */);
for (uint16_t i = 0; i < agcs.size(); ++i) {
- if (!agcs[i].has_value()) {
- continue;
- }
- const GnssAgc& gnssAgc = agcs[i].value();
+ const GnssAgc& gnssAgc = agcs[i];
jobject agcBuilderObject = env->NewObject(class_gnssAgcBuilder, method_gnssAgcBuilderCtor);
- env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetLevelDb,
- gnssAgc.agcLevelDb);
- env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetConstellationType,
- (int)gnssAgc.constellation);
- env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetCarrierFrequencyHz,
- gnssAgc.carrierFrequencyHz);
+ callObjectMethodIgnoringResult(env, agcBuilderObject, method_gnssAgcBuilderSetLevelDb,
+ gnssAgc.agcLevelDb);
+ callObjectMethodIgnoringResult(env, agcBuilderObject,
+ method_gnssAgcBuilderSetConstellationType,
+ (int)gnssAgc.constellation);
+ callObjectMethodIgnoringResult(env, agcBuilderObject,
+ method_gnssAgcBuilderSetCarrierFrequencyHz,
+ gnssAgc.carrierFrequencyHz);
jobject agcObject = env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderBuild);
env->SetObjectArrayElement(gnssAgcArray, i, agcObject);
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h
index 9b346312..17af949 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.h
+++ b/services/core/jni/gnss/GnssMeasurementCallback.h
@@ -62,8 +62,8 @@
jobjectArray translateAllGnssMeasurements(
JNIEnv* env, const std::vector<hardware::gnss::GnssMeasurement>& measurements);
- jobjectArray translateAllGnssAgcs(
- JNIEnv* env, const std::vector<std::optional<hardware::gnss::GnssData::GnssAgc>>& agcs);
+ jobjectArray translateAllGnssAgcs(JNIEnv* env,
+ const std::vector<hardware::gnss::GnssData::GnssAgc>& agcs);
void translateAndSetGnssData(const hardware::gnss::GnssData& data);
diff --git a/services/core/jni/gnss/Utils.cpp b/services/core/jni/gnss/Utils.cpp
index 40a94ce..8f32c47 100644
--- a/services/core/jni/gnss/Utils.cpp
+++ b/services/core/jni/gnss/Utils.cpp
@@ -111,6 +111,13 @@
}
}
+void callObjectMethodIgnoringResult(JNIEnv* env, jobject obj, jmethodID mid, ...) {
+ va_list args;
+ va_start(args, mid);
+ env->DeleteLocalRef(env->CallObjectMethodV(obj, mid, args));
+ va_end(args);
+}
+
JavaObject::JavaObject(JNIEnv* env, jclass clazz, jmethodID defaultCtor)
: env_(env), clazz_(clazz) {
object_ = env_->NewObject(clazz_, defaultCtor);
diff --git a/services/core/jni/gnss/Utils.h b/services/core/jni/gnss/Utils.h
index 2640a77..c8ee661 100644
--- a/services/core/jni/gnss/Utils.h
+++ b/services/core/jni/gnss/Utils.h
@@ -56,6 +56,8 @@
void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
+void callObjectMethodIgnoringResult(JNIEnv* env, jobject obj, jmethodID mid, ...);
+
template <class T>
void logHidlError(hardware::Return<T>& result, const char* errorMessage) {
ALOGE("%s HIDL transport error: %s", errorMessage, result.description().c_str());
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 26c442d..e18e002 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -23,6 +23,7 @@
import android.content.ComponentName;
import android.os.FileUtils;
import android.os.PersistableBundle;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
@@ -83,7 +84,7 @@
private static final String ATTR_NEW_USER_DISCLAIMER = "new-user-disclaimer";
// Values of ATTR_NEW_USER_DISCLAIMER
- static final String NEW_USER_DISCLAIMER_SHOWN = "shown";
+ static final String NEW_USER_DISCLAIMER_ACKNOWLEDGED = "acked";
static final String NEW_USER_DISCLAIMER_NOT_NEEDED = "not_needed";
static final String NEW_USER_DISCLAIMER_NEEDED = "needed";
@@ -613,6 +614,28 @@
}
}
+ boolean isNewUserDisclaimerAcknowledged() {
+ if (mNewUserDisclaimer == null) {
+ if (mUserId == UserHandle.USER_SYSTEM) {
+ return true;
+ }
+ Slogf.w(TAG, "isNewUserDisclaimerAcknowledged(%d): mNewUserDisclaimer is null",
+ mUserId);
+ return false;
+ }
+ switch (mNewUserDisclaimer) {
+ case NEW_USER_DISCLAIMER_ACKNOWLEDGED:
+ case NEW_USER_DISCLAIMER_NOT_NEEDED:
+ return true;
+ case NEW_USER_DISCLAIMER_NEEDED:
+ return false;
+ default:
+ Slogf.w(TAG, "isNewUserDisclaimerAcknowledged(%d): invalid value %d", mUserId,
+ mNewUserDisclaimer);
+ return false;
+ }
+ }
+
void dump(IndentingPrintWriter pw) {
pw.println();
pw.println("Enabled Device Admins (User " + mUserId + ", provisioningState: "
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 733cfcd..6f41d42 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -56,6 +56,8 @@
import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_ID;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
@@ -66,9 +68,12 @@
import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
@@ -3918,8 +3923,8 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(
- isProfileOwner(caller) || isDeviceOwner(caller) || isSystemUid(caller)
- || isPasswordLimitingAdminTargetingP(caller));
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isSystemUid(caller) || isPasswordLimitingAdminTargetingP(caller));
if (parent) {
Preconditions.checkCallAuthorization(
@@ -4772,7 +4777,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller));
Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
return !isSeparateProfileChallengeEnabled(caller.getUserId());
@@ -4860,12 +4866,12 @@
enforceUserUnlocked(caller.getUserId());
if (parent) {
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
"Only profile owner, device owner and system may call this method on parent.");
} else {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
- || isDeviceOwner(caller) || isProfileOwner(caller),
+ || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
"Must have " + REQUEST_PASSWORD_COMPLEXITY
+ " permission, or be a profile owner or device owner.");
}
@@ -4888,7 +4894,8 @@
"Provided complexity is not one of the allowed values.");
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
synchronized (getLockObject()) {
@@ -4968,7 +4975,7 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwner(caller));
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
@@ -5160,7 +5167,7 @@
}
// If caller has PO (or DO) throw or fail silently depending on its target SDK level.
- if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+ if (isDefaultDeviceOwner(caller) || isProfileOwner(caller)) {
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
if (getTargetSdk(admin.info.getPackageName(), userHandle) < Build.VERSION_CODES.O) {
@@ -5219,7 +5226,7 @@
return false;
}
- boolean callerIsDeviceOwnerAdmin = isDeviceOwner(caller);
+ boolean callerIsDeviceOwnerAdmin = isDefaultDeviceOwner(caller);
boolean doNotAskCredentialsOnBoot =
(flags & DevicePolicyManager.RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT) != 0;
if (callerIsDeviceOwnerAdmin && doNotAskCredentialsOnBoot) {
@@ -5406,7 +5413,8 @@
Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number.");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
// timeoutMs with value 0 means that the admin doesn't participate
// timeoutMs is clamped to the interval in case the internal constants change in the future
final long minimumStrongAuthTimeout = getMinimumStrongAuthTimeoutMs();
@@ -5575,7 +5583,8 @@
}
private boolean canManageCaCerts(CallerIdentity caller) {
- return (caller.hasAdminComponent() && (isDeviceOwner(caller) || isProfileOwner(caller)))
+ return (caller.hasAdminComponent() && (isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL))
|| hasCallingOrSelfPermission(MANAGE_CA_CERTIFICATES);
}
@@ -5689,7 +5698,7 @@
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(!isUserSelectable, "The credential "
@@ -5754,7 +5763,7 @@
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
@@ -5818,12 +5827,12 @@
}
private boolean canInstallCertificates(CallerIdentity caller) {
- return isProfileOwner(caller) || isDeviceOwner(caller)
+ return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
}
private boolean canChooseCertificates(CallerIdentity caller) {
- return isProfileOwner(caller) || isDeviceOwner(caller)
+ return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| isCallerDelegate(caller, DELEGATION_CERT_SELECTION);
}
@@ -5871,7 +5880,7 @@
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_SELECTION)));
final int granteeUid;
@@ -5984,7 +5993,7 @@
// If not, fall back to the device owner check.
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
+ isDefaultDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
}
@VisibleForTesting
@@ -6047,7 +6056,7 @@
enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags);
} else {
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
@@ -6182,7 +6191,7 @@
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
@@ -6337,14 +6346,15 @@
final int userId = caller.getUserId();
// Ensure calling process is device/profile owner.
if (!Collections.disjoint(scopes, DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| (isProfileOwner(caller) && isManagedProfile(caller.getUserId())));
} else if (!Collections.disjoint(
scopes, DEVICE_OWNER_OR_ORGANIZATION_OWNED_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| isProfileOwnerOfOrganizationOwnedDevice(caller));
} else {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
}
synchronized (getLockObject()) {
@@ -6434,7 +6444,8 @@
// * Either it's a profile owner / device owner, if componentName is provided
// * Or it's an app querying its own delegation scopes
if (caller.hasAdminComponent()) {
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
} else {
Preconditions.checkCallAuthorization(isPackage(caller, delegatePackage),
String.format("Caller with uid %d is not %s", caller.getUid(),
@@ -6467,7 +6478,8 @@
// Retrieve the user ID of the calling process.
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
return getDelegatePackagesInternalLocked(scope, caller.getUserId());
}
@@ -6600,7 +6612,8 @@
final CallerIdentity caller = getCallerIdentity(who);
// Ensure calling process is device/profile owner.
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final DevicePolicyData policy = getUserData(caller.getUserId());
@@ -6712,7 +6725,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_ALWAYS_ON_VPN_PACKAGE);
if (vpnPackage == null) {
@@ -6792,7 +6806,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return mInjector.binderWithCleanCallingIdentity(
() -> mInjector.getVpnManager().getAlwaysOnVpnPackageForUser(caller.getUserId()));
@@ -6818,7 +6833,8 @@
caller = getCallerIdentity();
} else {
caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
}
return mInjector.binderWithCleanCallingIdentity(
@@ -6841,7 +6857,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return mInjector.binderWithCleanCallingIdentity(
() -> mInjector.getVpnManager().getVpnLockdownAllowlist(caller.getUserId()));
@@ -6946,8 +6963,9 @@
+ "organization-owned device.");
}
if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || calledByProfileOwnerOnOrgOwnedDevice,
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || calledByProfileOwnerOnOrgOwnedDevice
+ || isFinancedDeviceOwner(caller),
"Only device owners or profile owners of organization-owned device can set "
+ "WIPE_RESET_PROTECTION_DATA");
}
@@ -7139,8 +7157,8 @@
}
Preconditions.checkNotNull(who, "ComponentName is null");
CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager
.OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY);
@@ -7189,7 +7207,8 @@
UserHandle.getUserId(frpManagementAgentUid));
} else {
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
admin = getProfileOwnerOrDeviceOwnerLocked(caller);
}
}
@@ -7617,7 +7636,7 @@
public void setRecommendedGlobalProxy(ComponentName who, ProxyInfo proxyInfo) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkAllUsersAreAffiliatedWithDevice();
mInjector.binderWithCleanCallingIdentity(
() -> mInjector.getConnectivityManager().setGlobalProxy(proxyInfo));
@@ -7915,7 +7934,8 @@
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -7934,8 +7954,7 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isProfileOwner(caller)
- || isDeviceOwner(caller)
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
synchronized (getLockObject()) {
@@ -7955,7 +7974,8 @@
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -7974,8 +7994,7 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isProfileOwner(caller)
- || isDeviceOwner(caller)
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
synchronized (getLockObject()) {
@@ -8067,7 +8086,7 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
@@ -8091,7 +8110,7 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
}
@@ -8108,7 +8127,7 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
@@ -8132,7 +8151,7 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
}
@@ -8161,7 +8180,7 @@
// which could still contain data related to that user. Should we disallow that, e.g. until
// next boot? Might not be needed given that this still requires user consent.
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkAllUsersAreAffiliatedWithDevice();
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REQUEST_BUGREPORT);
@@ -8472,7 +8491,8 @@
}
Objects.requireNonNull(packageList, "packageList is null");
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && isDefaultDeviceOwner(caller))
|| (caller.hasPackage()
&& isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_KEEP_UNINSTALLED_PACKAGES);
@@ -8501,7 +8521,8 @@
return null;
}
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && isDefaultDeviceOwner(caller))
|| (caller.hasPackage()
&& isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
@@ -8615,8 +8636,8 @@
@Override
public boolean hasDeviceOwner() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || canManageUsers(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || canManageUsers(caller) || isFinancedDeviceOwner(caller)
|| hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
return mOwners.hasDeviceOwner();
}
@@ -8633,17 +8654,29 @@
}
}
- private boolean isDeviceOwner(CallerIdentity caller) {
+ /**
+ * Returns {@code true} <b>only if</b> the caller is the device owner and the device owner type
+ * is {@link DevicePolicyManager#DEVICE_OWNER_TYPE_DEFAULT}. {@code false} is returned for the
+ * case where the caller is not the device owner, there is no device owner, or the device owner
+ * type is not {@link DevicePolicyManager#DEVICE_OWNER_TYPE_DEFAULT}.
+ *
+ */
+ private boolean isDefaultDeviceOwner(CallerIdentity caller) {
synchronized (getLockObject()) {
- if (!mOwners.hasDeviceOwner() || mOwners.getDeviceOwnerUserId() != caller.getUserId()) {
- return false;
- }
+ return isDeviceOwnerLocked(caller) && getDeviceOwnerTypeLocked(
+ mOwners.getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_DEFAULT;
+ }
+ }
- if (caller.hasAdminComponent()) {
- return mOwners.getDeviceOwnerComponent().equals(caller.getComponentName());
- } else {
- return isUidDeviceOwnerLocked(caller.getUid());
- }
+ private boolean isDeviceOwnerLocked(CallerIdentity caller) {
+ if (!mOwners.hasDeviceOwner() || mOwners.getDeviceOwnerUserId() != caller.getUserId()) {
+ return false;
+ }
+
+ if (caller.hasAdminComponent()) {
+ return mOwners.getDeviceOwnerComponent().equals(caller.getComponentName());
+ } else {
+ return isUidDeviceOwnerLocked(caller.getUid());
}
}
@@ -9073,8 +9106,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mLockPatternUtils.setDeviceOwnerInfo(info != null ? info.toString() : null));
@@ -9258,7 +9291,8 @@
final CallerIdentity caller = getCallerIdentity(who);
final int userId = caller.getUserId();
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
Preconditions.checkCallingUser(isManagedProfile(userId));
synchronized (getLockObject()) {
@@ -9289,7 +9323,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
mInjector.binderWithCleanCallingIdentity(() -> {
mUserManager.setUserName(caller.getUserId(), profileName);
@@ -9725,7 +9760,8 @@
}
private void enforceCanCallLockTaskLocked(CallerIdentity caller) {
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isProfileOwner(caller)
+ || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
final int userId = caller.getUserId();
if (!canUserUseLockTaskLocked(userId)) {
@@ -9982,7 +10018,8 @@
ComponentName activity) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isProfileOwner(caller)
+ || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
final int userHandle = caller.getUserId();
synchronized (getLockObject()) {
@@ -10009,7 +10046,8 @@
public void clearPackagePersistentPreferredActivities(ComponentName who, String packageName) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isProfileOwner(caller)
+ || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
final int userHandle = caller.getUserId();
synchronized (getLockObject()) {
@@ -10030,7 +10068,7 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
if (parent) {
mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
@@ -10070,7 +10108,7 @@
String packageName, Bundle settings) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
@@ -10168,7 +10206,8 @@
public void setRestrictionsProvider(ComponentName who, ComponentName permissionProvider) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_RESTRICTIONS_PROVIDER);
synchronized (getLockObject()) {
@@ -10193,7 +10232,8 @@
public void addCrossProfileIntentFilter(ComponentName who, IntentFilter filter, int flags) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
int callingUserId = caller.getUserId();
synchronized (getLockObject()) {
long id = mInjector.binderClearCallingIdentity();
@@ -10242,7 +10282,8 @@
public void clearCrossProfileIntentFilters(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
int callingUserId = caller.getUserId();
synchronized (getLockObject()) {
@@ -10382,7 +10423,8 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -10495,7 +10537,8 @@
+ "system input methods when called on the parent instance of an "
+ "organization-owned device");
} else {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
}
if (packageList != null) {
@@ -10553,7 +10596,8 @@
if (calledOnParentInstance) {
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
} else {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
}
synchronized (getLockObject()) {
@@ -10732,7 +10776,7 @@
// Only allow the system user to use this method
Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
"createAndManageUser was called from non-system user");
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0;
@@ -10934,10 +10978,11 @@
@Override
public void acknowledgeNewUserDisclaimer() {
CallerIdentity callerIdentity = getCallerIdentity();
- canManageUsers(callerIdentity);
+ Preconditions.checkCallAuthorization(canManageUsers(callerIdentity)
+ || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS));
setShowNewUserDisclaimer(callerIdentity.getUserId(),
- DevicePolicyData.NEW_USER_DISCLAIMER_SHOWN);
+ DevicePolicyData.NEW_USER_DISCLAIMER_ACKNOWLEDGED);
}
private void setShowNewUserDisclaimer(@UserIdInt int userId, String value) {
@@ -10970,11 +11015,23 @@
}
@Override
+ public boolean isNewUserDisclaimerAcknowledged() {
+ CallerIdentity callerIdentity = getCallerIdentity();
+ Preconditions.checkCallAuthorization(canManageUsers(callerIdentity)
+ || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS));
+ int userId = callerIdentity.getUserId();
+ synchronized (getLockObject()) {
+ DevicePolicyData policyData = getUserData(userId);
+ return policyData.isNewUserDisclaimerAcknowledged();
+ }
+ }
+
+ @Override
public boolean removeUser(ComponentName who, UserHandle userHandle) {
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(userHandle, "UserHandle is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REMOVE_USER);
return mInjector.binderWithCleanCallingIdentity(() -> {
@@ -11008,7 +11065,7 @@
public boolean switchUser(ComponentName who, UserHandle userHandle) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SWITCH_USER);
boolean switched = false;
@@ -11083,7 +11140,7 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(userHandle, "UserHandle is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_START_USER_IN_BACKGROUND);
final int userId = userHandle.getIdentifier();
@@ -11119,7 +11176,7 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(userHandle, "UserHandle is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_STOP_USER);
final int userId = userHandle.getIdentifier();
@@ -11135,7 +11192,8 @@
public int logoutUser(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_LOGOUT_USER);
final int callingUserId = caller.getUserId();
@@ -11165,8 +11223,8 @@
@Override
public int logoutUserInternal() {
CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(
- canManageUsers(caller) || hasCallingOrSelfPermission(permission.CREATE_USERS));
+ Preconditions.checkCallAuthorization(canManageUsers(caller)
+ || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS));
int result = logoutUserUnchecked(getCurrentForegroundUserId());
Slogf.d(LOG_TAG, "logout called by uid %d. Result: %d", caller.getUid(), result);
@@ -11224,7 +11282,7 @@
public List<UserHandle> getSecondaryUsers(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
return mInjector.binderWithCleanCallingIdentity(() -> {
final List<UserInfo> userInfos = mInjector.getUserManager().getAliveUsers();
@@ -11244,7 +11302,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return mInjector.binderWithCleanCallingIdentity(
() -> mInjector.getUserManager().isUserEphemeral(caller.getUserId()));
@@ -11255,7 +11314,7 @@
String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
return mInjector.binderWithCleanCallingIdentity(() -> {
@@ -11306,7 +11365,7 @@
Objects.requireNonNull(packageNames, "array of packages cannot be null");
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED);
@@ -11369,7 +11428,7 @@
public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
synchronized (getLockObject()) {
@@ -11390,8 +11449,8 @@
public List<String> listPolicyExemptApps() {
CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS) || isDeviceOwner(caller)
- || isProfileOwner(caller));
+ hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)
+ || isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return listPolicyExemptAppsUnchecked();
}
@@ -11432,12 +11491,19 @@
final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
getProfileOwnerOrDeviceOwnerLocked(caller), parent);
- if (isDeviceOwner(caller)) {
+ if (isDefaultDeviceOwner(caller)) {
if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
throw new SecurityException("Device owner cannot set user restriction " + key);
}
Preconditions.checkArgument(!parent,
"Cannot use the parent instance in Device Owner mode");
+ } else if (isFinancedDeviceOwner(caller)) {
+ if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
+ throw new SecurityException("Cannot set user restriction " + key
+ + " when managing a financed device");
+ }
+ Preconditions.checkArgument(!parent,
+ "Cannot use the parent instance in Financed Device Owner mode");
} else {
boolean profileOwnerCanChangeOnItself = !parent
&& UserRestrictionsUtils.canProfileOwnerChange(key, userHandle);
@@ -11546,7 +11612,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller)
|| (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
synchronized (getLockObject()) {
@@ -11561,7 +11628,7 @@
boolean hidden, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
List<String> exemptApps = listPolicyExemptAppsUnchecked();
@@ -11607,7 +11674,7 @@
String packageName, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
@@ -11643,7 +11710,7 @@
public void enableSystemApp(ComponentName who, String callerPackage, String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)));
synchronized (getLockObject()) {
@@ -11687,7 +11754,7 @@
public int enableSystemAppWithIntent(ComponentName who, String callerPackage, Intent intent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)));
int numberOfAppsInstalled = 0;
@@ -11756,7 +11823,7 @@
String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage()
&& isCallerDelegate(caller, DELEGATION_INSTALL_EXISTING_PACKAGE)));
@@ -11872,7 +11939,8 @@
boolean uninstallBlocked) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isFinancedDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_BLOCK_UNINSTALL)));
final int userId = caller.getUserId();
@@ -11913,7 +11981,8 @@
if (who != null) {
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(
- isProfileOwner(caller) || isDeviceOwner(caller));
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isFinancedDeviceOwner(caller));
}
try {
return mIPackageManager.getBlockUninstallForUser(packageName, userId);
@@ -12087,7 +12156,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -12111,7 +12181,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -12136,7 +12207,8 @@
// Check can set secondary lockscreen enabled
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()),
"User %d is not allowed to call setSecondaryLockscreenEnabled",
caller.getUserId());
@@ -12325,6 +12397,7 @@
final int userHandle = caller.getUserId();
synchronized (getLockObject()) {
enforceCanCallLockTaskLocked(caller);
+ enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags);
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
setLockTaskFeaturesLocked(userHandle, flags);
}
@@ -12373,6 +12446,24 @@
});
}
+ private void enforceCanSetLockTaskFeaturesOnFinancedDevice(CallerIdentity caller, int flags) {
+ int allowedFlags = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
+ | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+ | LOCK_TASK_FEATURE_NOTIFICATIONS;
+
+ if (!isFinancedDeviceOwner(caller)) {
+ return;
+ }
+
+ if ((flags == 0) || ((flags & ~(allowedFlags)) != 0)) {
+ throw new SecurityException(
+ "Permitted lock task features when managing a financed device: "
+ + "LOCK_TASK_FEATURE_SYSTEM_INFO, LOCK_TASK_FEATURE_KEYGUARD, "
+ + "LOCK_TASK_FEATURE_HOME, LOCK_TASK_FEATURE_GLOBAL_ACTIONS, "
+ + "or LOCK_TASK_FEATURE_NOTIFICATIONS");
+ }
+ }
+
@Override
public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) {
Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
@@ -12413,7 +12504,7 @@
public void setGlobalSetting(ComponentName who, String setting, String value) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_GLOBAL_SETTING)
@@ -12454,7 +12545,8 @@
Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkStringNotEmpty(setting, "String setting is null or empty");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING);
synchronized (getLockObject()) {
@@ -12476,8 +12568,8 @@
Preconditions.checkNotNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
@@ -12498,8 +12590,8 @@
Preconditions.checkNotNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
return mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0);
@@ -12510,7 +12602,7 @@
Preconditions.checkNotNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
UserHandle userHandle = caller.getUserHandle();
if (mIsAutomotive && !locationEnabled) {
@@ -12592,8 +12684,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
// Don't allow set time when auto time is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
@@ -12612,8 +12704,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
// Don't allow set timezone when auto timezone is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
@@ -12633,7 +12725,8 @@
public void setSecureSetting(ComponentName who, String setting, String value) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
int callingUserId = caller.getUserId();
synchronized (getLockObject()) {
@@ -12720,7 +12813,8 @@
public void setMasterVolumeMuted(ComponentName who, boolean on) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_MASTER_VOLUME_MUTED);
synchronized (getLockObject()) {
@@ -12737,7 +12831,8 @@
public boolean isMasterVolumeMuted(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
AudioManager audioManager =
@@ -12750,7 +12845,8 @@
public void setUserIcon(ComponentName who, Bitmap icon) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
mInjector.binderWithCleanCallingIdentity(
@@ -12766,7 +12862,8 @@
public boolean setKeyguardDisabled(ComponentName who, boolean disabled) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
final int userId = caller.getUserId();
synchronized (getLockObject()) {
@@ -12808,7 +12905,8 @@
@Override
public boolean setStatusBarDisabled(ComponentName who, boolean disabled) {
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
int userId = caller.getUserId();
synchronized (getLockObject()) {
@@ -13042,7 +13140,7 @@
@Override
public boolean isActiveDeviceOwner(int uid) {
- return isDeviceOwner(new CallerIdentity(uid, null, null));
+ return isDefaultDeviceOwner(new CallerIdentity(uid, null, null));
}
@Override
@@ -13633,7 +13731,7 @@
synchronized (getLockObject()) {
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY);
if (policy == null) {
@@ -13831,7 +13929,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return mOwners.getSystemUpdateInfo();
}
@@ -13840,7 +13939,7 @@
public void setPermissionPolicy(ComponentName admin, String callerPackage, int policy) {
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_POLICY);
@@ -13882,11 +13981,15 @@
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isFinancedDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_GRANT_STATE);
synchronized (getLockObject()) {
+ if (isFinancedDeviceOwner(caller)) {
+ enforceCanSetPermissionGrantOnFinancedDevice(packageName, permission);
+ }
long ident = mInjector.binderClearCallingIdentity();
try {
boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
@@ -13946,12 +14049,23 @@
}
}
+ private void enforceCanSetPermissionGrantOnFinancedDevice(
+ String packageName, String permission) {
+ if (!Manifest.permission.READ_PHONE_STATE.equals(permission)) {
+ throw new SecurityException("Cannot grant " + permission
+ + " when managing a financed device");
+ } else if (!mOwners.getDeviceOwnerPackageName().equals(packageName)) {
+ throw new SecurityException("Cannot grant permission to a package that is not"
+ + " the device owner");
+ }
+ }
+
@Override
public int getPermissionGrantState(ComponentName admin, String callerPackage,
String packageName, String permission) throws RemoteException {
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
Preconditions.checkCallAuthorization(isSystemUid(caller) || (caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
synchronized (getLockObject()) {
@@ -14231,7 +14345,7 @@
}
private void checkIsDeviceOwner(CallerIdentity caller) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller), caller.getUid()
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller), caller.getUid()
+ " is not device owner");
}
@@ -14263,8 +14377,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
return mInjector.binderWithCleanCallingIdentity(() -> {
String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses();
@@ -14299,7 +14413,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return isManagedProfile(caller.getUserId());
}
@@ -14308,7 +14423,7 @@
public void reboot(ComponentName admin) {
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REBOOT);
mInjector.binderWithCleanCallingIdentity(() -> {
// Make sure there are no ongoing calls on the device.
@@ -14542,7 +14657,8 @@
return null;
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || canManageUsers(caller) || isFinancedDeviceOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
return deviceOwnerAdmin == null ? null : deviceOwnerAdmin.organizationName;
@@ -14576,7 +14692,8 @@
Objects.requireNonNull(who);
Objects.requireNonNull(packageNames);
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller),
"Admin %s does not own the profile", caller.getComponentName());
if (!mHasFeature) {
@@ -14627,7 +14744,8 @@
return new ArrayList<>();
}
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller),
"Admin %s does not own the profile", caller.getComponentName());
synchronized (getLockObject()) {
@@ -14766,7 +14884,8 @@
final Set<String> affiliationIds = new ArraySet<>(ids);
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
final int callingUserId = caller.getUserId();
synchronized (getLockObject()) {
@@ -14797,7 +14916,8 @@
Objects.requireNonNull(admin);
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
return new ArrayList<String>(getUserData(caller.getUserId()).mAffiliationIds);
@@ -14891,7 +15011,7 @@
if (admin != null) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
} else {
// A delegate app passes a null admin component, which is expected
Preconditions.checkCallAuthorization(
@@ -14928,7 +15048,7 @@
if (admin != null) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
} else {
// A delegate app passes a null admin component, which is expected
Preconditions.checkCallAuthorization(
@@ -14961,7 +15081,7 @@
if (admin != null) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
} else {
// A delegate app passes a null admin component, which is expected
Preconditions.checkCallAuthorization(
@@ -15007,7 +15127,7 @@
if (admin != null) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
} else {
// A delegate app passes a null admin component, which is expected
Preconditions.checkCallAuthorization(
@@ -15246,7 +15366,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller) || isFinancedDeviceOwner(caller));
toggleBackupServiceActive(caller.getUserId(), enabled);
}
@@ -15259,7 +15380,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller) || isFinancedDeviceOwner(caller));
return mInjector.binderWithCleanCallingIdentity(() -> {
synchronized (getLockObject()) {
@@ -15334,7 +15456,8 @@
}
Objects.requireNonNull(admin);
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final int callingUserId = caller.getUserId();
@@ -15462,7 +15585,7 @@
final boolean isManagedProfileOwner = isProfileOwner(caller)
&& isManagedProfile(caller.getUserId());
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isDeviceOwner(caller) || isManagedProfileOwner))
+ && (isDefaultDeviceOwner(caller) || isManagedProfileOwner))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
synchronized (getLockObject()) {
@@ -15622,7 +15745,7 @@
}
final CallerIdentity caller = getCallerIdentity(admin, packageName);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isDeviceOwner(caller)
+ && (isDefaultDeviceOwner(caller)
|| (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))
|| hasCallingOrSelfPermission(permission.MANAGE_USERS));
@@ -15654,7 +15777,7 @@
final boolean isManagedProfileOwner = isProfileOwner(caller)
&& isManagedProfile(caller.getUserId());
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isDeviceOwner(caller) || isManagedProfileOwner))
+ && (isDefaultDeviceOwner(caller) || isManagedProfileOwner))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
if (mOwners.hasDeviceOwner()) {
checkAllUsersAreAffiliatedWithDevice();
@@ -15815,14 +15938,16 @@
@Override
public long getLastSecurityLogRetrievalTime() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || canManageUsers(caller));
return getUserData(UserHandle.USER_SYSTEM).mLastSecurityLogRetrievalTime;
}
@Override
public long getLastBugReportRequestTime() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || canManageUsers(caller));
return getUserData(UserHandle.USER_SYSTEM).mLastBugReportRequestTime;
}
@@ -15830,7 +15955,7 @@
public long getLastNetworkLogRetrievalTime() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))
|| canManageUsers(caller));
final int affectedUserId = getNetworkLoggingAffectedUser();
@@ -15846,7 +15971,8 @@
throw new IllegalArgumentException("token must be at least 32-byte long");
}
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final int userHandle = caller.getUserId();
@@ -15870,7 +15996,8 @@
return false;
}
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final int userHandle = caller.getUserId();
@@ -15895,7 +16022,8 @@
return false;
}
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
return isResetPasswordTokenActiveForUserLocked(caller.getUserId());
@@ -15920,7 +16048,8 @@
Objects.requireNonNull(token);
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
DevicePolicyData policy = getUserData(caller.getUserId());
@@ -15945,7 +16074,7 @@
@Override
public boolean isCurrentInputMethodSetByOwner() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| isProfileOwner(caller) || isSystemUid(caller),
"Only profile owner, device owner and system may call this method.");
return getUserData(caller.getUserId()).mCurrentInputMethodSet;
@@ -15956,7 +16085,7 @@
final int userId = user.getIdentifier();
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization((userId == caller.getUserId())
- || isProfileOwner(caller) || isDeviceOwner(caller)
+ || isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| hasFullCrossUsersPermission(caller, userId));
synchronized (getLockObject()) {
@@ -15973,7 +16102,8 @@
Objects.requireNonNull(callback, "callback is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CLEAR_APPLICATION_USER_DATA);
long ident = mInjector.binderClearCallingIdentity();
@@ -16005,7 +16135,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOGOUT_ENABLED);
synchronized (getLockObject()) {
@@ -16054,7 +16184,8 @@
"Provided administrator and target have the same package name.");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
final int callingUserId = caller.getUserId();
final DevicePolicyData policy = getUserData(callingUserId);
@@ -16096,7 +16227,7 @@
if (isUserAffiliatedWithDeviceLocked(callingUserId)) {
notifyAffiliatedProfileTransferOwnershipComplete(callingUserId);
}
- } else if (isDeviceOwner(caller)) {
+ } else if (isDefaultDeviceOwner(caller)) {
ownerType = ADMIN_TYPE_DEVICE_OWNER;
prepareTransfer(admin, target, bundle, callingUserId,
ADMIN_TYPE_DEVICE_OWNER);
@@ -16177,7 +16308,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
final String startUserSessionMessageString =
startUserSessionMessage != null ? startUserSessionMessage.toString() : null;
@@ -16202,7 +16333,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
final String endUserSessionMessageString =
endUserSessionMessage != null ? endUserSessionMessage.toString() : null;
@@ -16227,7 +16358,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
@@ -16242,7 +16373,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
@@ -16258,7 +16389,8 @@
@Nullable
public PersistableBundle getTransferOwnershipBundle() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final int callingUserId = caller.getUserId();
@@ -16288,7 +16420,7 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(apnSetting, "ApnSetting is null in addOverrideApn");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
if (tm != null) {
@@ -16309,7 +16441,7 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(apnSetting, "ApnSetting is null in updateOverrideApn");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
if (apnId < 0) {
return false;
@@ -16331,7 +16463,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
return removeOverrideApnUnchecked(apnId);
}
@@ -16352,7 +16484,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
return getOverrideApnsUnchecked();
}
@@ -16373,7 +16505,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_OVERRIDE_APNS_ENABLED);
setOverrideApnsEnabledUnchecked(enabled);
@@ -16393,7 +16525,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
Cursor enforceCursor = mInjector.binderWithCleanCallingIdentity(
() -> mContext.getContentResolver().query(
@@ -16476,7 +16608,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkAllUsersAreAffiliatedWithDevice();
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_GLOBAL_PRIVATE_DNS);
@@ -16515,7 +16647,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
final int currentMode = ConnectivitySettingsManager.getPrivateDnsMode(mContext);
switch (currentMode) {
@@ -16537,7 +16669,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
return mInjector.settingsGlobalGetString(PRIVATE_DNS_SPECIFIER);
}
@@ -16547,8 +16679,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE);
DevicePolicyEventLogger
@@ -16923,7 +17055,8 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(packages, "packages is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(
DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES);
@@ -16942,7 +17075,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
synchronized (getLockObject()) {
return mOwners.getDeviceOwnerProtectedPackages(who.getPackageName());
@@ -16954,7 +17088,7 @@
Objects.requireNonNull(who, "Admin component name must be provided");
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"Common Criteria mode can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
synchronized (getLockObject()) {
@@ -16974,7 +17108,7 @@
if (who != null) {
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"Common Criteria mode can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
@@ -17446,7 +17580,7 @@
final CallerIdentity caller = getCallerIdentity(callerPackage);
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwner(caller)
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller)
|| isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
synchronized (getLockObject()) {
@@ -17467,7 +17601,7 @@
final CallerIdentity caller = getCallerIdentity(callerPackage);
// Only the DPC can set this ID.
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller),
"Only a Device Owner or Profile Owner may set the Enterprise ID.");
// Empty enterprise ID must not be provided in calls to this method.
Preconditions.checkArgument(!TextUtils.isEmpty(organizationId),
@@ -17559,6 +17693,13 @@
? Collections.emptySet()
: mOverlayPackagesProvider.getNonRequiredApps(
admin, caller.getUserId(), ACTION_PROVISION_MANAGED_PROFILE);
+ if (nonRequiredApps.isEmpty()) {
+ Slogf.i(LOG_TAG, "No disallowed packages for the managed profile.");
+ } else {
+ for (String packageName : nonRequiredApps) {
+ Slogf.i(LOG_TAG, "Disallowed package [" + packageName + "]");
+ }
+ }
userInfo = mUserManager.createProfileForUserEvenWhenDisallowed(
provisioningParams.getProfileName(),
UserManager.USER_TYPE_PROFILE_MANAGED,
@@ -17577,6 +17718,8 @@
startTime,
callerPackage);
+ onCreateAndProvisionManagedProfileStarted(provisioningParams);
+
installExistingAdminPackage(userInfo.id, admin.getPackageName());
if (!enableAdminAndSetProfileOwner(
userInfo.id, caller.getUserId(), admin, provisioningParams.getOwnerName())) {
@@ -17597,6 +17740,8 @@
}
}
+ onCreateAndProvisionManagedProfileCompleted(provisioningParams);
+
sendProvisioningCompletedBroadcast(
userInfo.id,
ACTION_PROVISION_MANAGED_PROFILE,
@@ -17618,6 +17763,29 @@
}
}
+ /**
+ * Callback called at the beginning of {@link #createAndProvisionManagedProfile(
+ * ManagedProfileProvisioningParams, String)} after the relevant prechecks have passed.
+ *
+ * <p>The logic in this method blocks provisioning.
+ *
+ * <p>This method is meant to be overridden by OEMs.
+ */
+ private void onCreateAndProvisionManagedProfileStarted(
+ ManagedProfileProvisioningParams provisioningParams) {}
+
+ /**
+ * Callback called at the end of {@link #createAndProvisionManagedProfile(
+ * ManagedProfileProvisioningParams, String)} after all the other provisioning tasks
+ * have completed successfully.
+ *
+ * <p>The logic in this method blocks provisioning.
+ *
+ * <p>This method is meant to be overridden by OEMs.
+ */
+ private void onCreateAndProvisionManagedProfileCompleted(
+ ManagedProfileProvisioningParams provisioningParams) {}
+
private void resetInteractAcrossProfilesAppOps() {
mInjector.getCrossProfileApps().clearInteractAcrossProfilesAppOps();
pregrantDefaultInteractAcrossProfilesAppOps();
@@ -17857,6 +18025,7 @@
ERROR_PRE_CONDITION_FAILED,
"Provisioning preconditions failed with result: " + result);
}
+ onProvisionFullyManagedDeviceStarted(provisioningParams);
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
@@ -17882,6 +18051,7 @@
disallowAddUser();
setAdminCanGrantSensorsPermissionForUserUnchecked(deviceOwnerUserId,
provisioningParams.canDeviceOwnerGrantSensorsPermissions());
+ onProvisionFullyManagedDeviceCompleted(provisioningParams);
sendProvisioningCompletedBroadcast(
deviceOwnerUserId,
ACTION_PROVISION_MANAGED_DEVICE,
@@ -17897,6 +18067,29 @@
}
}
+ /**
+ * Callback called at the beginning of {@link #provisionFullyManagedDevice(
+ * FullyManagedDeviceProvisioningParams, String)} after the relevant prechecks have passed.
+ *
+ * <p>The logic in this method blocks provisioning.
+ *
+ * <p>This method is meant to be overridden by OEMs.
+ */
+ private void onProvisionFullyManagedDeviceStarted(
+ FullyManagedDeviceProvisioningParams provisioningParams) {}
+
+ /**
+ * Callback called at the end of {@link #provisionFullyManagedDevice(
+ * FullyManagedDeviceProvisioningParams, String)} after all the other provisioning tasks
+ * have completed successfully.
+ *
+ * <p>The logic in this method blocks provisioning.
+ *
+ * <p>This method is meant to be overridden by OEMs.
+ */
+ private void onProvisionFullyManagedDeviceCompleted(
+ FullyManagedDeviceProvisioningParams provisioningParams) {}
+
private void setTimeAndTimezone(String timeZone, long localTime) {
try {
final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
@@ -18141,27 +18334,55 @@
@DeviceOwnerType int deviceOwnerType) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
- verifyDeviceOwnerTypePreconditions(admin);
-
- final String packageName = admin.getPackageName();
- Preconditions.checkState(!mOwners.isDeviceOwnerTypeSetForDeviceOwner(packageName),
- "The device owner type has already been set for " + packageName);
synchronized (getLockObject()) {
- mOwners.setDeviceOwnerType(packageName, deviceOwnerType);
+ setDeviceOwnerTypeLocked(admin, deviceOwnerType);
}
}
+ private void setDeviceOwnerTypeLocked(ComponentName admin,
+ @DeviceOwnerType int deviceOwnerType) {
+ String packageName = admin.getPackageName();
+ boolean isAdminTestOnly;
+
+ verifyDeviceOwnerTypePreconditionsLocked(admin);
+
+ isAdminTestOnly = isAdminTestOnlyLocked(admin, mOwners.getDeviceOwnerUserId());
+ Preconditions.checkState(isAdminTestOnly
+ || !mOwners.isDeviceOwnerTypeSetForDeviceOwner(packageName),
+ "Test only admins can only set the device owner type more than once");
+
+ mOwners.setDeviceOwnerType(packageName, deviceOwnerType, isAdminTestOnly);
+ }
+
@Override
@DeviceOwnerType
public int getDeviceOwnerType(@NonNull ComponentName admin) {
- verifyDeviceOwnerTypePreconditions(admin);
synchronized (getLockObject()) {
- return mOwners.getDeviceOwnerType(admin.getPackageName());
+ verifyDeviceOwnerTypePreconditionsLocked(admin);
+ return getDeviceOwnerTypeLocked(admin.getPackageName());
}
}
- private void verifyDeviceOwnerTypePreconditions(@NonNull ComponentName admin) {
+ @DeviceOwnerType
+ private int getDeviceOwnerTypeLocked(String packageName) {
+ return mOwners.getDeviceOwnerType(packageName);
+ }
+
+ /**
+ * {@code true} is returned <b>only if</b> the caller is the device owner and the device owner
+ * type is {@link DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}. {@code false} is returned for
+ * the case where the caller is not the device owner, there is no device owner, or the device
+ * owner type is not {@link DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}.
+ */
+ private boolean isFinancedDeviceOwner(CallerIdentity caller) {
+ synchronized (getLockObject()) {
+ return isDeviceOwnerLocked(caller) && getDeviceOwnerTypeLocked(
+ mOwners.getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_FINANCED;
+ }
+ }
+
+ private void verifyDeviceOwnerTypePreconditionsLocked(@NonNull ComponentName admin) {
Preconditions.checkState(mOwners.hasDeviceOwner(), "there is no device owner");
Preconditions.checkState(mOwners.getDeviceOwnerComponent().equals(admin),
"admin is not the device owner");
@@ -18172,7 +18393,7 @@
Objects.requireNonNull(packageName, "Admin package name must be provided");
final CallerIdentity caller = getCallerIdentity(packageName);
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"USB data signaling can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
Preconditions.checkState(canUsbDataSignalingBeDisabled(),
@@ -18213,7 +18434,7 @@
synchronized (getLockObject()) {
// If the caller is an admin, return the policy set by itself. Otherwise
// return the device-wide policy.
- if (isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+ if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) {
return getProfileOwnerOrDeviceOwnerLocked(caller).mUsbDataSignalingEnabled;
} else {
return isUsbDataSignalingEnabledInternalLocked();
@@ -18254,7 +18475,7 @@
public void setMinimumRequiredWifiSecurityLevel(int level) {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"Wi-Fi minimum security level can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
@@ -18284,7 +18505,7 @@
public void setSsidAllowlist(List<String> ssids) {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"SSID allowlist can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
@@ -18306,10 +18527,11 @@
public List<String> getSsidAllowlist() {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isSystemUid(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
+ || canQueryAdminPolicy(caller),
"SSID allowlist can only be retrieved by a device owner or "
- + "a profile owner on an organization-owned device or a system app.");
+ + "a profile owner on an organization-owned device or "
+ + "an app with the QUERY_ADMIN_POLICY permission.");
synchronized (getLockObject()) {
final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
UserHandle.USER_SYSTEM);
@@ -18322,7 +18544,7 @@
public void setSsidDenylist(List<String> ssids) {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"SSID denylist can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
@@ -18344,10 +18566,11 @@
public List<String> getSsidDenylist() {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isSystemUid(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
+ || canQueryAdminPolicy(caller),
"SSID denylist can only be retrieved by a device owner or "
- + "a profile owner on an organization-owned device or a system app.");
+ + "a profile owner on an organization-owned device or "
+ + "an app with the QUERY_ADMIN_POLICY permission.");
synchronized (getLockObject()) {
final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
UserHandle.USER_SYSTEM);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index 685cf05..598f9e8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -34,6 +34,7 @@
import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -41,6 +42,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.Binder;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.view.inputmethod.InputMethodInfo;
@@ -91,6 +93,8 @@
List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
String getActiveApexPackageNameContainingPackage(String packageName);
+
+ String getDeviceManagerRoleHolderPackageName(Context context);
}
private static final class DefaultInjector implements Injector {
@@ -104,6 +108,19 @@
public String getActiveApexPackageNameContainingPackage(String packageName) {
return ApexManager.getInstance().getActiveApexPackageNameContainingPackage(packageName);
}
+
+ @Override
+ public String getDeviceManagerRoleHolderPackageName(Context context) {
+ return Binder.withCleanCallingIdentity(() -> {
+ RoleManager roleManager = context.getSystemService(RoleManager.class);
+ List<String> roleHolders =
+ roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_MANAGER);
+ if (roleHolders.isEmpty()) {
+ return null;
+ }
+ return roleHolders.get(0);
+ });
+ }
}
@VisibleForTesting
@@ -142,9 +159,20 @@
nonRequiredApps.addAll(getDisallowedApps(provisioningAction));
nonRequiredApps.removeAll(
getRequiredAppsMainlineModules(nonRequiredApps, provisioningAction));
+ nonRequiredApps.removeAll(getDeviceManagerRoleHolders());
return nonRequiredApps;
}
+ private Set<String> getDeviceManagerRoleHolders() {
+ HashSet<String> result = new HashSet<>();
+ String deviceManagerRoleHolderPackageName =
+ mInjector.getDeviceManagerRoleHolderPackageName(mContext);
+ if (deviceManagerRoleHolderPackageName != null) {
+ result.add(deviceManagerRoleHolderPackageName);
+ }
+ return result;
+ }
+
/**
* Returns a subset of {@code packageNames} whose packages are mainline modules declared as
* required apps via their app metadata.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 3584728..fe8f223 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -637,13 +637,15 @@
}
}
- void setDeviceOwnerType(String packageName, @DeviceOwnerType int deviceOwnerType) {
+ void setDeviceOwnerType(String packageName, @DeviceOwnerType int deviceOwnerType,
+ boolean isAdminTestOnly) {
synchronized (mLock) {
if (!hasDeviceOwner()) {
Slog.e(TAG, "Attempting to set a device owner type when there is no device owner");
return;
- } else if (isDeviceOwnerTypeSetForDeviceOwner(packageName)) {
- Slog.e(TAG, "Device owner type for " + packageName + " has already been set");
+ } else if (!isAdminTestOnly && isDeviceOwnerTypeSetForDeviceOwner(packageName)) {
+ Slog.e(TAG, "Setting the device owner type more than once is only allowed"
+ + " for test only admins");
return;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 74e04ed..f4fae2f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -154,6 +154,7 @@
import com.android.server.os.SchedulingPolicyService;
import com.android.server.people.PeopleService;
import com.android.server.pm.ApexManager;
+import com.android.server.pm.ApexSystemServiceInfo;
import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.DataLoaderManagerService;
import com.android.server.pm.DynamicCodeLoggingService;
@@ -176,6 +177,7 @@
import com.android.server.powerstats.PowerStatsService;
import com.android.server.profcollect.ProfcollectForwardingService;
import com.android.server.recoverysystem.RecoverySystemService;
+import com.android.server.resources.ResourcesManagerService;
import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.role.RoleServicePlatformHelper;
import com.android.server.rotationresolver.RotationResolverManagerService;
@@ -223,8 +225,8 @@
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
+import java.util.List;
import java.util.Locale;
-import java.util.Map;
import java.util.Timer;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
@@ -265,8 +267,6 @@
"/apex/com.android.os.statsd/javalib/service-statsd.jar";
private static final String CONNECTIVITY_SERVICE_APEX_PATH =
"/apex/com.android.tethering/javalib/service-connectivity.jar";
- private static final String NEARBY_SERVICE_APEX_PATH =
- "/apex/com.android.nearby/javalib/service-nearby.jar";
private static final String STATS_COMPANION_LIFECYCLE_CLASS =
"com.android.server.stats.StatsCompanion$Lifecycle";
private static final String STATS_PULL_ATOM_SERVICE_CLASS =
@@ -277,8 +277,6 @@
"com.android.server.usb.UsbService$Lifecycle";
private static final String MIDI_SERVICE_CLASS =
"com.android.server.midi.MidiService$Lifecycle";
- private static final String NEARBY_SERVICE_CLASS =
- "com.android.server.nearby.NearbyService";
private static final String WIFI_APEX_SERVICE_JAR_PATH =
"/apex/com.android.wifi/javalib/service-wifi.jar";
private static final String WIFI_SERVICE_CLASS =
@@ -369,6 +367,8 @@
"com.android.server.adb.AdbService$Lifecycle";
private static final String SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS =
"com.android.server.speech.SpeechRecognitionManagerService";
+ private static final String WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS =
+ "com.android.server.wallpapereffectsgeneration.WallpaperEffectsGenerationManagerService";
private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS =
"com.android.server.appprediction.AppPredictionManagerService";
private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
@@ -1289,6 +1289,13 @@
mSystemServiceManager.startService(new OverlayManagerService(mSystemContext));
t.traceEnd();
+ // Manages Resources packages
+ t.traceBegin("StartResourcesManagerService");
+ ResourcesManagerService resourcesService = new ResourcesManagerService(mSystemContext);
+ resourcesService.setActivityManagerService(mActivityManagerService);
+ mSystemServiceManager.startService(resourcesService);
+ t.traceEnd();
+
t.traceBegin("StartSensorPrivacyService");
mSystemServiceManager.startService(new SensorPrivacyService(mSystemContext));
t.traceEnd();
@@ -1466,7 +1473,7 @@
// TelecomLoader hooks into classes with defined HFP logic,
// so check for either telephony or microphone.
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) ||
- mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
t.traceBegin("StartTelecomLoaderService");
mSystemServiceManager.startService(TelecomLoaderService.class);
t.traceEnd();
@@ -1474,7 +1481,7 @@
t.traceBegin("StartTelephonyRegistry");
telephonyRegistry = new TelephonyRegistry(
- context, new TelephonyRegistry.ConfigurationProvider());
+ context, new TelephonyRegistry.ConfigurationProvider());
ServiceManager.addService("telephony.registry", telephonyRegistry);
t.traceEnd();
@@ -1891,7 +1898,6 @@
}
t.traceEnd();
-
t.traceBegin("StartIpSecService");
try {
ipSecService = IpSecService.create(context);
@@ -2000,16 +2006,6 @@
}
t.traceEnd();
- // Start Nearby Service.
- t.traceBegin("StartNearbyService");
- try {
- mSystemServiceManager.startServiceFromJar(NEARBY_SERVICE_CLASS,
- NEARBY_SERVICE_APEX_PATH);
- } catch (Throwable e) {
- reportWtf("starting NearbyService", e);
- }
- t.traceEnd();
-
t.traceBegin("StartConnectivityService");
// This has to be called after NetworkManagementService, NetworkStatsService
// and NetworkPolicyManager because ConnectivityService needs to take these
@@ -2132,6 +2128,14 @@
Slog.i(TAG, "Wallpaper service disabled by config");
}
+ // WallpaperEffectsGeneration manager service
+ // TODO (b/135218095): Use deviceHasConfigString(context,
+ // R.string.config_defaultWallpaperEffectsGenerationService)
+ t.traceBegin("StartWallpaperEffectsGenerationService");
+ mSystemServiceManager.startService(
+ WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+
t.traceBegin("StartAudioService");
if (!isArc) {
mSystemServiceManager.startService(AudioService.Lifecycle.class);
@@ -3014,7 +3018,9 @@
t.traceEnd();
t.traceBegin("MakeTelephonyRegistryReady");
try {
- if (telephonyRegistryF != null) telephonyRegistryF.systemRunning();
+ if (telephonyRegistryF != null) {
+ telephonyRegistryF.systemRunning();
+ }
} catch (Throwable e) {
reportWtf("Notifying TelephonyRegistry running", e);
}
@@ -3079,10 +3085,12 @@
*/
private void startApexServices(@NonNull TimingsTraceAndSlog t) {
t.traceBegin("startApexServices");
- Map<String, String> services = ApexManager.getInstance().getApexSystemServices();
- // TODO(satayev): introduce android:order for services coming the same apexes
- for (String name : new TreeSet<>(services.keySet())) {
- String jarPath = services.get(name);
+ // TODO(b/192880996): get the list from "android" package, once the manifest entries
+ // are migrated to system manifest.
+ List<ApexSystemServiceInfo> services = ApexManager.getInstance().getApexSystemServices();
+ for (ApexSystemServiceInfo info : services) {
+ String name = info.getName();
+ String jarPath = info.getJarPath();
t.traceBegin("starting " + name);
if (TextUtils.isEmpty(jarPath)) {
mSystemServiceManager.startService(name);
diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
index 16d6241..0a9b7b1 100644
--- a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
+++ b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
@@ -32,7 +32,7 @@
name: "test_com.android.server",
manifest: "manifest.json",
androidManifest: "AndroidManifest.xml",
- java_libs: ["FakeApexSystemService"],
+ java_libs: ["FakeApexSystemServices"],
file_contexts: ":apex.test-file_contexts",
key: "test_com.android.server.key",
updatable: false,
diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
index eb741ca..6bec284 100644
--- a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
+++ b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
@@ -21,21 +21,29 @@
<application android:hasCode="false" android:testOnly="true">
<apex-system-service
android:name="com.android.server.testing.FakeApexSystemService"
- android:path="/apex/test_com.android.server/javalib/FakeApexSystemService.jar"
- android:minSdkVersion="30"/>
+ android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar"
+ android:minSdkVersion="30"
+ />
+
+ <apex-system-service
+ android:name="com.android.server.testing.FakeApexSystemService2"
+ android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar"
+ android:minSdkVersion="30"
+ android:initOrder="1"
+ />
<!-- Always inactive system service, since maxSdkVersion is low -->
<apex-system-service
- android:name="com.android.apex.test.OldApexSystemService"
- android:path="/apex/com.android.apex.test/javalib/fake.jar"
+ android:name="com.android.server.testing.OldApexSystemService"
+ android:path="/apex/test_com.android.server/javalib/fake.jar"
android:minSdkVersion="1"
android:maxSdkVersion="1"
/>
<!-- Always inactive system service, since minSdkVersion is high -->
<apex-system-service
- android:name="com.android.apex.test.NewApexSystemService"
- android:path="/apex/com.android.apex.test/javalib/fake.jar"
+ android:name="com.android.server.testing.NewApexSystemService"
+ android:path="/apex/test_com.android.server/javalib/fake.jar"
android:minSdkVersion="999999"
/>
</application>
diff --git a/services/tests/apexsystemservices/service/Android.bp b/services/tests/apexsystemservices/services/Android.bp
similarity index 94%
rename from services/tests/apexsystemservices/service/Android.bp
rename to services/tests/apexsystemservices/services/Android.bp
index 9d04f39..477ea4c 100644
--- a/services/tests/apexsystemservices/service/Android.bp
+++ b/services/tests/apexsystemservices/services/Android.bp
@@ -8,7 +8,7 @@
}
java_library {
- name: "FakeApexSystemService",
+ name: "FakeApexSystemServices",
srcs: ["**/*.java"],
sdk_version: "system_server_current",
libs: [
diff --git a/services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java
similarity index 100%
rename from services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java
rename to services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java
diff --git a/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java
new file mode 100644
index 0000000..e83343b
--- /dev/null
+++ b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java
@@ -0,0 +1,41 @@
+/*
+ * 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.server.testing;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.SystemService;
+
+/**
+ * A fake system service that just logs when it is started.
+ */
+public class FakeApexSystemService2 extends SystemService {
+
+ private static final String TAG = "FakeApexSystemService";
+
+ public FakeApexSystemService2(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ Log.d(TAG, "FakeApexSystemService2 onStart");
+ }
+}
diff --git a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
index 2b453a9..10635a1 100644
--- a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
+++ b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
@@ -37,9 +37,15 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
+import java.util.Objects;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
@RunWith(DeviceJUnit4ClassRunner.class)
public class ApexSystemServicesTestCases extends BaseHostJUnit4Test {
+ private static final int REBOOT_TIMEOUT = 1 * 60 * 1000;
+
private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
private final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
private final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, this::getDevice);
@@ -67,7 +73,7 @@
}
@Test
- public void noApexSystemServerStartsWithoutApex() throws Exception {
+ public void testNoApexSystemServiceStartsWithoutApex() throws Exception {
mPreparer.reboot();
assertThat(getFakeApexSystemServiceLogcat())
@@ -75,20 +81,55 @@
}
@Test
- public void apexSystemServerStarts() throws Exception {
+ public void testApexSystemServiceStarts() throws Exception {
// Pre-install the apex
String apex = "test_com.android.server.apex";
mPreparer.pushResourceFile(apex, "/system/apex/" + apex);
// Reboot activates the apex
mPreparer.reboot();
+ mDevice.waitForBootComplete(REBOOT_TIMEOUT);
+
assertThat(getFakeApexSystemServiceLogcat())
.contains("FakeApexSystemService onStart");
}
+ @Test
+ public void testInitOrder() throws Exception {
+ // Pre-install the apex
+ String apex = "test_com.android.server.apex";
+ mPreparer.pushResourceFile(apex, "/system/apex/" + apex);
+ // Reboot activates the apex
+ mPreparer.reboot();
+
+ mDevice.waitForBootComplete(REBOOT_TIMEOUT);
+
+ assertThat(getFakeApexSystemServiceLogcat().lines()
+ .map(ApexSystemServicesTestCases::getDebugMessage)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()))
+ .containsExactly(
+ // Second service has a higher initOrder and must be started first
+ "FakeApexSystemService2 onStart",
+ "FakeApexSystemService onStart"
+ )
+ .inOrder();
+ }
+
private String getFakeApexSystemServiceLogcat() throws DeviceNotAvailableException {
return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", "FakeApexSystemService:D",
"*:S");
}
+ private static final Pattern DEBUG_MESSAGE =
+ Pattern.compile("(FakeApexSystemService[0-9]* onStart)");
+
+ private static String getDebugMessage(String logcatLine) {
+ return DEBUG_MESSAGE.matcher(logcatLine)
+ .results()
+ .map(m -> m.group(1))
+ .findFirst()
+ .orElse(null);
+ }
+
}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 9e221be..75669d5 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -52,6 +52,7 @@
"service-blobstore",
"service-jobscheduler",
"service-permission.impl",
+ "service-supplementalprocess.impl",
"services.core",
"services.devicepolicy",
"services.net",
diff --git a/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_disabled.xml b/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_disabled.xml
new file mode 100644
index 0000000..eb15451
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_disabled.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<game-mode-config
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:supportsPerformanceGameMode="false"
+ android:supportsBatteryGameMode="false"
+ android:allowGameAngleDriver="false"
+ android:allowGameDownscaling="false"
+ android:allowGameFpsOverride="false"
+/>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_enabled.xml b/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_enabled.xml
new file mode 100644
index 0000000..65b7467
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_enabled.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<game-mode-config
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:supportsPerformanceGameMode="false"
+ android:supportsBatteryGameMode="false"
+ android:allowGameAngleDriver="true"
+ android:allowGameDownscaling="true"
+ android:allowGameFpsOverride="true"
+/>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java
index fec9b12..e89c812 100644
--- a/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java
+++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java
@@ -359,6 +359,34 @@
LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder();
}
+ @Test
+ public void dispatchTransientVisibilityChanged_valueUnchanged_doesNotInvokeCallback() {
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false);
+
+ assertThat(mGameSession.mCapturedTransientSystemBarVisibilityFromRevealGestures).hasSize(0);
+ }
+
+ @Test
+ public void dispatchTransientVisibilityChanged_valueChanged_invokesCallback() {
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true);
+
+ assertThat(mGameSession.mCapturedTransientSystemBarVisibilityFromRevealGestures)
+ .containsExactly(true).inOrder();
+ }
+
+ @Test
+ public void dispatchTransientVisibilityChanged_manyTimes_invokesCallbackWhenValueChanges() {
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false);
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true);
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false);
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false);
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true);
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true);
+
+ assertThat(mGameSession.mCapturedTransientSystemBarVisibilityFromRevealGestures)
+ .containsExactly(true, false, true).inOrder();
+ }
+
private static class LifecycleTrackingGameSession extends GameSession {
private enum LifecycleMethodCall {
ON_CREATE,
@@ -368,6 +396,8 @@
}
final List<LifecycleMethodCall> mLifecycleMethodCalls = new ArrayList<>();
+ final List<Boolean> mCapturedTransientSystemBarVisibilityFromRevealGestures =
+ new ArrayList<>();
@Override
public void onCreate() {
@@ -387,5 +417,11 @@
mLifecycleMethodCalls.add(LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED);
}
}
+
+ @Override
+ public void onTransientSystemBarVisibilityFromRevealGestureChanged(
+ boolean visibleDueToGesture) {
+ mCapturedTransientSystemBarVisibilityFromRevealGestures.add(visibleDueToGesture);
+ }
}
}
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 28c91aa..7670953 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.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 @@
@Mock private PermissionManagerServiceInternal mPermissionManagerServiceInternal;
@Mock private MediaSessionManager mMediaSessionManager;
@Mock private RoleManager mRoleManager;
+ @Mock private TelephonyManager mTelephonyManager;
private long mCurrentTimeMillis;
@@ -2309,6 +2311,11 @@
}
@Override
+ TelephonyManager getTelephonyManager() {
+ return mTelephonyManager;
+ }
+
+ @Override
AppFGSTracker getAppFGSTracker() {
return mAppFGSTracker;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index eed2d42..d2358a0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -23,20 +23,32 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.GameManager;
import android.app.GameModeInfo;
+import android.app.GameState;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.hardware.power.Mode;
import android.os.Bundle;
+import android.os.PowerManagerInternal;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
@@ -46,6 +58,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import org.junit.After;
@@ -76,6 +89,8 @@
private TestLooper mTestLooper;
@Mock
private PackageManager mMockPackageManager;
+ @Mock
+ private PowerManagerInternal mMockPowerManager;
// Stolen from ConnectivityServiceTest.MockContext
class MockContext extends ContextWrapper {
@@ -149,19 +164,27 @@
mPackageName = mMockContext.getPackageName();
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+ applicationInfo.packageName = mPackageName;
final PackageInfo pi = new PackageInfo();
pi.packageName = mPackageName;
pi.applicationInfo = applicationInfo;
final List<PackageInfo> packages = new ArrayList<>();
packages.add(pi);
+
+ final Resources resources =
+ InstrumentationRegistry.getInstrumentation().getContext().getResources();
+ when(mMockPackageManager.getResourcesForApplication(anyString()))
+ .thenReturn(resources);
when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
.thenReturn(packages);
when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
.thenReturn(applicationInfo);
+ LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
}
@After
public void tearDown() throws Exception {
+ LocalServices.removeServiceForTest(PowerManagerInternal.class);
GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.disableCompatScale(mPackageName);
if (mMockingSession != null) {
@@ -310,6 +333,46 @@
.thenReturn(applicationInfo);
}
+ private void mockInterventionsEnabledFromXml() throws Exception {
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+ Bundle metaDataBundle = new Bundle();
+ final int resId = 123;
+ metaDataBundle.putInt(
+ GameManagerService.GamePackageConfiguration.METADATA_GAME_MODE_CONFIG, resId);
+ applicationInfo.metaData = metaDataBundle;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ seedGameManagerServiceMetaDataFromFile(mPackageName, resId,
+ "res/xml/gama_manager_service_metadata_config_enabled.xml");
+ }
+
+ private void mockInterventionsDisabledFromXml() throws Exception {
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+ Bundle metaDataBundle = new Bundle();
+ final int resId = 123;
+ metaDataBundle.putInt(
+ GameManagerService.GamePackageConfiguration.METADATA_GAME_MODE_CONFIG, resId);
+ applicationInfo.metaData = metaDataBundle;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ seedGameManagerServiceMetaDataFromFile(mPackageName, resId,
+ "res/xml/gama_manager_service_metadata_config_disabled.xml");
+ }
+
+
+ private void seedGameManagerServiceMetaDataFromFile(String packageName, int resId,
+ String fileName)
+ throws Exception {
+ AssetManager assetManager =
+ InstrumentationRegistry.getInstrumentation().getContext().getAssets();
+ XmlResourceParser xmlResourceParser =
+ assetManager.openXmlResourceParser(fileName);
+ when(mMockPackageManager.getXml(eq(packageName), eq(resId), any()))
+ .thenReturn(xmlResourceParser);
+ }
+
/**
* By default game mode is not supported.
*/
@@ -499,8 +562,8 @@
gameManagerService.getConfig(mPackageName);
assertEquals(config.getGameModeConfiguration(gameMode).getUseAngle(), angleEnabled);
- // Validate GameManagerService.getAngleEnabled() returns the correct value.
- assertEquals(gameManagerService.getAngleEnabled(mPackageName, USER_ID_1), angleEnabled);
+ // Validate GameManagerService.isAngleEnabled() returns the correct value.
+ assertEquals(gameManagerService.isAngleEnabled(mPackageName, USER_ID_1), angleEnabled);
}
private void checkFps(GameManagerService gameManagerService, int gameMode, int fps) {
@@ -511,7 +574,7 @@
}
GameManagerService.GamePackageConfiguration config =
gameManagerService.getConfig(mPackageName);
- assertEquals(config.getGameModeConfiguration(gameMode).getFps(), fps);
+ assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
}
/**
@@ -893,6 +956,36 @@
}
@Test
+ public void testGameModeConfigAllowFpsTrue() throws Exception {
+ mockDeviceConfigAll();
+ mockModifyGameModeGranted();
+ mockInterventionsEnabledFromXml();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
+ startUser(gameManagerService, USER_ID_1);
+ GameManagerService.GamePackageConfiguration config =
+ gameManagerService.getConfig(mPackageName);
+ assertEquals(90,
+ config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
+ assertEquals(30, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
+ }
+
+ @Test
+ public void testGameModeConfigAllowFpsFalse() throws Exception {
+ mockDeviceConfigAll();
+ mockModifyGameModeGranted();
+ mockInterventionsDisabledFromXml();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
+ startUser(gameManagerService, USER_ID_1);
+ GameManagerService.GamePackageConfiguration config =
+ gameManagerService.getConfig(mPackageName);
+ assertEquals(0,
+ config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
+ assertEquals(0, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
+ }
+
+ @Test
public void testInterventionFps() throws Exception {
mockDeviceConfigAll();
mockModifyGameModeGranted();
@@ -1052,4 +1145,41 @@
assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameModeInfo.getActiveGameMode());
assertEquals(0, gameModeInfo.getAvailableGameModes().length);
}
+
+ @Test
+ public void testGameStateLoadingRequiresPerformanceMode() {
+ mockDeviceConfigNone();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService =
+ new GameManagerService(mMockContext, mTestLooper.getLooper());
+ startUser(gameManagerService, USER_ID_1);
+ GameState gameState = new GameState(true, GameState.MODE_NONE);
+ gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+ mTestLooper.dispatchAll();
+ verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+ }
+
+ private void setGameState(boolean isLoading) {
+ mockDeviceConfigNone();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService =
+ new GameManagerService(mMockContext, mTestLooper.getLooper());
+ startUser(gameManagerService, USER_ID_1);
+ gameManagerService.setGameMode(
+ mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ GameState gameState = new GameState(isLoading, GameState.MODE_NONE);
+ gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+ mTestLooper.dispatchAll();
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, isLoading);
+ }
+
+ @Test
+ public void testSetGameStateLoading() {
+ setGameState(true);
+ }
+
+ @Test
+ public void testSetGameStateNotLoading() {
+ setGameState(false);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index d5e4710..08de62b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -26,13 +26,14 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
+import android.Manifest;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
@@ -70,6 +71,7 @@
import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
import com.android.internal.util.Preconditions;
import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
import com.android.server.wm.WindowManagerService;
import org.junit.After;
@@ -83,6 +85,7 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Objects;
/**
@@ -109,6 +112,7 @@
private static final ComponentName GAME_B_MAIN_ACTIVITY =
new ComponentName(GAME_B_PACKAGE, "com.package.game.b.MainActivity");
+
private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
private MockitoSession mMockingSession;
@@ -121,13 +125,14 @@
private WindowManagerInternal mMockWindowManagerInternal;
@Mock
private IActivityManager mMockActivityManager;
- private FakeContext mFakeContext;
+ private MockContext mMockContext;
private FakeGameClassifier mFakeGameClassifier;
private FakeGameService mFakeGameService;
private FakeServiceConnector<IGameService> mFakeGameServiceConnector;
private FakeGameSessionService mFakeGameSessionService;
private FakeServiceConnector<IGameSessionService> mFakeGameSessionServiceConnector;
private ArrayList<ITaskStackListener> mTaskStackListeners;
+ private ArrayList<TaskSystemBarsListener> mTaskSystemBarsListeners;
private ArrayList<RunningTaskInfo> mRunningTaskInfos;
@Mock
@@ -140,7 +145,7 @@
.strictness(Strictness.LENIENT)
.startMocking();
- mFakeContext = new FakeContext(InstrumentationRegistry.getInstrumentation().getContext());
+ mMockContext = new MockContext(InstrumentationRegistry.getInstrumentation().getContext());
mFakeGameClassifier = new FakeGameClassifier();
mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE);
@@ -156,20 +161,30 @@
mTaskStackListeners.add(invocation.getArgument(0));
return null;
}).when(mMockActivityTaskManager).registerTaskStackListener(any());
-
- mRunningTaskInfos = new ArrayList<>();
- when(mMockActivityTaskManager.getTasks(anyInt(), anyBoolean(), anyBoolean())).thenReturn(
- mRunningTaskInfos);
-
doAnswer(invocation -> {
mTaskStackListeners.remove(invocation.getArgument(0));
return null;
}).when(mMockActivityTaskManager).unregisterTaskStackListener(any());
+ mTaskSystemBarsListeners = new ArrayList<>();
+ doAnswer(invocation -> {
+ mTaskSystemBarsListeners.add(invocation.getArgument(0));
+ return null;
+ }).when(mMockWindowManagerInternal).registerTaskSystemBarsListener(any());
+ doAnswer(invocation -> {
+ mTaskSystemBarsListeners.remove(invocation.getArgument(0));
+ return null;
+ }).when(mMockWindowManagerInternal).unregisterTaskSystemBarsListener(any());
+
+ mRunningTaskInfos = new ArrayList<>();
+ when(mMockActivityTaskManager.getTasks(anyInt(), anyBoolean(), anyBoolean())).thenReturn(
+ mRunningTaskInfos);
+
+
mGameServiceProviderInstance = new GameServiceProviderInstanceImpl(
new UserHandle(USER_ID),
ConcurrentUtils.DIRECT_EXECUTOR,
- mFakeContext,
+ mMockContext,
mFakeGameClassifier,
mMockActivityManager,
mMockActivityTaskManager,
@@ -301,6 +316,7 @@
throws Exception {
mGameServiceProviderInstance.start();
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
@@ -322,6 +338,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSessionService.CapturedCreateInvocation capturedCreateInvocation =
@@ -336,6 +353,7 @@
mGameServiceProviderInstance.start();
dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
@@ -345,6 +363,7 @@
public void gameTaskStartedAndSessionRequested_createsGameSession() throws Exception {
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -362,7 +381,9 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
CreateGameSessionRequest expectedCreateGameSessionRequest = new CreateGameSessionRequest(10,
@@ -376,6 +397,7 @@
public void gameSessionSuccessfullyCreated_createsTaskOverlay() throws Exception {
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -384,13 +406,67 @@
.complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10));
- verifyNoMoreInteractions(mMockWindowManagerInternal);
+ }
+
+ @Test
+ public void taskSystemBarsListenerChanged_noAssociatedGameSession_doesNothing() {
+ mGameServiceProviderInstance.start();
+
+ dispatchTaskSystemBarsEvent(taskSystemBarsListener -> {
+ taskSystemBarsListener.onTransientSystemBarsVisibilityChanged(
+ 10,
+ /* areVisible= */ false,
+ /* wereRevealedFromSwipeOnSystemBar= */ false);
+ });
+ }
+
+ @Test
+ public void systemBarsTransientShownDueToGesture_hasGameSession_propagatesToGameSession() {
+ mGameServiceProviderInstance.start();
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+ dispatchTaskSystemBarsEvent(taskSystemBarsListener -> {
+ taskSystemBarsListener.onTransientSystemBarsVisibilityChanged(
+ 10,
+ /* areVisible= */ true,
+ /* wereRevealedFromSwipeOnSystemBar= */ true);
+ });
+
+ assertThat(gameSession10.mAreTransientSystemBarsVisibleFromRevealGesture).isTrue();
+ }
+
+ @Test
+ public void systemBarsTransientShownButNotGesture_hasGameSession_notPropagatedToGameSession() {
+ mGameServiceProviderInstance.start();
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+ dispatchTaskSystemBarsEvent(taskSystemBarsListener -> {
+ taskSystemBarsListener.onTransientSystemBarsVisibilityChanged(
+ 10,
+ /* areVisible= */ true,
+ /* wereRevealedFromSwipeOnSystemBar= */ false);
+ });
+
+ assertThat(gameSession10.mAreTransientSystemBarsVisibleFromRevealGesture).isFalse();
}
@Test
public void gameTaskFocused_propagatedToGameSession() throws Exception {
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -416,6 +492,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -432,6 +509,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
dispatchTaskRemoved(10);
@@ -449,6 +527,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -466,6 +545,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -477,7 +557,6 @@
verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10));
verify(mMockWindowManagerInternal).removeTaskOverlay(eq(10), eq(mockSurfacePackage10));
- verifyNoMoreInteractions(mMockWindowManagerInternal);
}
@Test
@@ -486,6 +565,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -513,6 +593,7 @@
startTask(10, GAME_A_MAIN_ACTIVITY);
startTask(11, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -530,6 +611,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -557,6 +639,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -586,6 +669,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -619,10 +703,19 @@
}
@Test
+ public void createGameSession_failurePermissionDenied() throws Exception {
+ mGameServiceProviderInstance.start();
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionDenied(Manifest.permission.MANAGE_GAME_ACTIVITY);
+ assertThrows(SecurityException.class, () -> mFakeGameService.requestCreateGameSession(10));
+ }
+
+ @Test
public void stop_severalActiveGameSessions_destroysGameSessionsAndUnbinds() throws Exception {
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -650,6 +743,7 @@
public void takeScreenshot_failureNoBitmapCaptured() throws Exception {
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
IGameSessionController gameSessionController = getOnlyElement(
@@ -669,6 +763,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
IGameSessionController gameSessionController = getOnlyElement(
@@ -683,6 +778,7 @@
@Test
public void restartGame_taskIdAssociatedWithGame_restartsTargetGame() throws Exception {
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
Intent launchIntent = new Intent("com.test.ACTION_LAUNCH_GAME_PACKAGE")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
when(mMockPackageManager.getLaunchIntentForPackage(GAME_A_PACKAGE))
@@ -691,6 +787,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -710,14 +807,16 @@
.mGameSessionController.restartGame(10);
verify(mMockActivityManager).forceStopPackage(GAME_A_PACKAGE, UserHandle.USER_CURRENT);
- assertThat(mFakeContext.getLastStartedIntent()).isEqualTo(launchIntent);
+ assertThat(mMockContext.getLastStartedIntent()).isEqualTo(launchIntent);
}
@Test
public void restartGame_taskIdNotAssociatedWithGame_noOp() throws Exception {
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -730,7 +829,20 @@
.mGameSessionController.restartGame(11);
verifyZeroInteractions(mMockActivityManager);
- assertThat(mFakeContext.getLastStartedIntent()).isNull();
+ assertThat(mMockContext.getLastStartedIntent()).isNull();
+ }
+
+ @Test
+ public void restartGame_failurePermissionDenied() throws Exception {
+ mGameServiceProviderInstance.start();
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+ IGameSessionController gameSessionController = Objects.requireNonNull(getOnlyElement(
+ mFakeGameSessionService.getCapturedCreateInvocations())).mGameSessionController;
+ mockPermissionDenied(Manifest.permission.MANAGE_GAME_ACTIVITY);
+ assertThrows(SecurityException.class,
+ () -> gameSessionController.restartGame(10));
}
private void startTask(int taskId, ComponentName componentName) {
@@ -774,6 +886,21 @@
}
}
+ private void mockPermissionGranted(String permission) {
+ mMockContext.setPermission(permission, PackageManager.PERMISSION_GRANTED);
+ }
+
+ private void mockPermissionDenied(String permission) {
+ mMockContext.setPermission(permission, PackageManager.PERMISSION_DENIED);
+ }
+
+ private void dispatchTaskSystemBarsEvent(
+ ThrowingConsumer<TaskSystemBarsListener> taskSystemBarsListenerConsumer) {
+ for (TaskSystemBarsListener listener : mTaskSystemBarsListeners) {
+ taskSystemBarsListenerConsumer.accept(listener);
+ }
+ }
+
static final class FakeGameService extends IGameService.Stub {
private IGameServiceController mGameServiceController;
@@ -888,6 +1015,7 @@
private static class FakeGameSession extends IGameSession.Stub {
boolean mIsDestroyed = false;
boolean mIsFocused = false;
+ boolean mAreTransientSystemBarsVisibleFromRevealGesture = false;
@Override
public void onDestroyed() {
@@ -898,15 +1026,35 @@
public void onTaskFocusChanged(boolean focused) {
mIsFocused = focused;
}
+
+ @Override
+ public void onTransientSystemBarVisibilityFromRevealGestureChanged(boolean areVisible) {
+ mAreTransientSystemBarsVisibleFromRevealGesture = areVisible;
+ }
}
- private final class FakeContext extends ContextWrapper {
+ private final class MockContext extends ContextWrapper {
private Intent mLastStartedIntent;
+ // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
+ private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
- FakeContext(Context base) {
+ MockContext(Context base) {
super(base);
}
+ /**
+ * Mock checks for the specified permission, and have them behave as per {@code granted}.
+ *
+ * <p>Passing null reverts to default behavior, which does a real permission check on the
+ * test package.
+ *
+ * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
+ * {@link PackageManager#PERMISSION_DENIED}.
+ */
+ public void setPermission(String permission, Integer granted) {
+ mMockedPermissions.put(permission, granted);
+ }
+
@Override
public PackageManager getPackageManager() {
return mMockPackageManager;
@@ -919,12 +1067,19 @@
@Override
public void enforceCallingPermission(String permission, @Nullable String message) {
- // Do nothing.
+ final Integer granted = mMockedPermissions.get(permission);
+ if (granted == null) {
+ super.enforceCallingOrSelfPermission(permission, message);
+ return;
+ }
+
+ if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
+ throw new SecurityException("[Test] permission denied: " + permission);
+ }
}
Intent getLastStartedIntent() {
return mLastStartedIntent;
}
}
-
}
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 bdeb2b4..f9bdad6 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 @@
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 153ce17..9d6793c 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 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 @@
@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 @@
// 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 @@
// 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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 6ae0031..4ec1641 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -69,6 +69,7 @@
import com.android.server.pm.pkg.parsing.ParsingPackage
import com.android.server.pm.pkg.parsing.ParsingPackageUtils
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
+import com.android.server.supplementalprocess.SupplementalProcessManagerLocal
import com.android.server.testutils.TestHandler
import com.android.server.testutils.mock
import com.android.server.testutils.nullable
@@ -335,6 +336,7 @@
stageServicesExtensionScan()
stageSystemSharedLibraryScan()
stagePermissionsControllerScan()
+ stageSupplementalProcessScan()
stageInstantAppResolverScan()
}
@@ -569,6 +571,22 @@
}
@Throws(Exception::class)
+ private fun stageSupplementalProcessScan() {
+ stageScanNewPackage("com.android.supplemental.process",
+ 1L, systemPartitions[0].privAppFolder,
+ withPackage = { pkg: PackageImpl ->
+ val applicationInfo: ApplicationInfo = createBasicApplicationInfo(pkg)
+ mockQueryServices(SupplementalProcessManagerLocal.SERVICE_INTERFACE,
+ createBasicServiceInfo(
+ pkg, applicationInfo, "SupplementalProcessService"))
+ pkg
+ },
+ withSetting = { setting: PackageSettingBuilder ->
+ setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM)
+ })
+ }
+
+ @Throws(Exception::class)
private fun stageSystemSharedLibraryScan() {
stageScanNewPackage("android.ext.shared",
1L, systemPartitions[0].appFolder,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index bdfdf77..64657a9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -50,7 +50,7 @@
import android.util.SparseArray;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.Preconditions;
@@ -101,12 +101,12 @@
mMockitoSession = ExtendedMockito.mockitoSession()
.strictness(Strictness.LENIENT)
.mockStatic(SystemProperties.class)
- .mockStatic(PackageHelper.class)
+ .mockStatic(InstallLocationUtils.class)
.startMocking();
when(mStorageManager.supportsCheckpoint()).thenReturn(true);
when(mStorageManager.needsCheckpoint()).thenReturn(true);
- when(PackageHelper.getStorageManager()).thenReturn(mStorageManager);
+ when(InstallLocationUtils.getStorageManager()).thenReturn(mStorageManager);
when(SystemProperties.get(eq("ro.apex.updatable"))).thenReturn("true");
when(SystemProperties.get(eq("ro.apex.updatable"), anyString())).thenReturn("true");
diff --git a/services/tests/servicestests/src/com/android/server/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
new file mode 100644
index 0000000..c325778
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+import android.os.Looper;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.R;
+import com.android.internal.util.test.BroadcastInterceptingContext;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.ExecutionException;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DockObserverTest {
+
+ @Rule
+ public TestableContext mContext =
+ new TestableContext(ApplicationProvider.getApplicationContext(), null);
+
+ private final BroadcastInterceptingContext mInterceptingContext =
+ new BroadcastInterceptingContext(mContext);
+
+ BroadcastInterceptingContext.FutureIntent updateExtconDockState(DockObserver observer,
+ String extconDockState) {
+ BroadcastInterceptingContext.FutureIntent futureIntent =
+ mInterceptingContext.nextBroadcastIntent(Intent.ACTION_DOCK_EVENT);
+ observer.setDockStateFromProviderForTesting(
+ DockObserver.ExtconStateProvider.fromString(extconDockState));
+ TestableLooper.get(this).processAllMessages();
+ return futureIntent;
+ }
+
+ DockObserver observerWithMappingConfig(String[] configEntries) {
+ mContext.getOrCreateTestableResources().addOverride(
+ R.array.config_dockExtconStateMapping,
+ configEntries);
+ return new DockObserver(mInterceptingContext);
+ }
+
+ void assertDockEventIntentWithExtraThenUndock(DockObserver observer, String extconDockState,
+ int expectedExtra) throws ExecutionException, InterruptedException {
+ assertThat(updateExtconDockState(observer, extconDockState)
+ .get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+ .isEqualTo(expectedExtra);
+ assertThat(updateExtconDockState(observer, "DOCK=0")
+ .get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+ .isEqualTo(Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ }
+
+ @Before
+ public void setUp() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Test
+ public void testDockIntentBroadcast_onlyAfterBootReady()
+ throws ExecutionException, InterruptedException {
+ DockObserver observer = new DockObserver(mInterceptingContext);
+ BroadcastInterceptingContext.FutureIntent futureIntent =
+ updateExtconDockState(observer, "DOCK=1");
+ updateExtconDockState(observer, "DOCK=1").assertNotReceived();
+ // Last boot phase reached
+ observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+ TestableLooper.get(this).processAllMessages();
+ assertThat(futureIntent.get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+ .isEqualTo(Intent.EXTRA_DOCK_STATE_DESK);
+ }
+
+ @Test
+ public void testDockIntentBroadcast_customConfigResource()
+ throws ExecutionException, InterruptedException {
+ DockObserver observer = observerWithMappingConfig(
+ new String[] {"2,KEY1=1,KEY2=2", "3,KEY3=3"});
+ observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+
+ // Mapping should not match
+ assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1",
+ Intent.EXTRA_DOCK_STATE_DESK);
+ assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY1=1",
+ Intent.EXTRA_DOCK_STATE_DESK);
+ assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY2=2",
+ Intent.EXTRA_DOCK_STATE_DESK);
+
+ // 1st mapping now matches
+ assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY2=2\nKEY1=1",
+ Intent.EXTRA_DOCK_STATE_CAR);
+
+ // 2nd mapping now matches
+ assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY3=3",
+ Intent.EXTRA_DOCK_STATE_LE_DESK);
+ }
+
+ @Test
+ public void testDockIntentBroadcast_customConfigResourceWithWildcard()
+ throws ExecutionException, InterruptedException {
+ DockObserver observer = observerWithMappingConfig(new String[] {
+ "2,KEY2=2",
+ "3,KEY3=3",
+ "4"
+ });
+ observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+ assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY5=5",
+ Intent.EXTRA_DOCK_STATE_HE_DESK);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index 9ee1205..3890d4d 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -24,14 +24,20 @@
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.fail;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
+import android.attention.AttentionManagerInternal.ProximityCallbackInternal;
import android.content.ComponentName;
import android.content.Context;
import android.os.IBinder;
@@ -42,6 +48,7 @@
import android.provider.DeviceConfig;
import android.service.attention.IAttentionCallback;
import android.service.attention.IAttentionService;
+import android.service.attention.IProximityCallback;
import androidx.test.filters.SmallTest;
@@ -49,6 +56,7 @@
import com.android.server.attention.AttentionManagerService.AttentionCheckCache;
import com.android.server.attention.AttentionManagerService.AttentionCheckCacheBuffer;
import com.android.server.attention.AttentionManagerService.AttentionHandler;
+import com.android.server.attention.AttentionManagerService.ProximityUpdate;
import org.junit.Before;
import org.junit.Test;
@@ -59,10 +67,13 @@
/**
* Tests for {@link com.android.server.attention.AttentionManagerService}
*/
+@SuppressWarnings("GuardedBy")
@SmallTest
public class AttentionManagerServiceTest {
+ private static final double PROXIMITY_SUCCESS_STATE = 1.0;
private AttentionManagerService mSpyAttentionManager;
private final int mTimeout = 1000;
+ private final Object mLock = new Object();
@Mock
private AttentionCallbackInternal mMockAttentionCallbackInternal;
@Mock
@@ -73,6 +84,8 @@
private IThermalService mMockIThermalService;
@Mock
Context mContext;
+ @Mock
+ private ProximityCallbackInternal mMockProximityCallbackInternal;
@Before
public void setUp() throws RemoteException {
@@ -84,7 +97,6 @@
doReturn(true).when(mMockIPowerManager).isInteractive();
mPowerManager = new PowerManager(mContext, mMockIPowerManager, mMockIThermalService, null);
- Object mLock = new Object();
// setup a spy on attention manager
AttentionManagerService attentionManager = new AttentionManagerService(
mContext,
@@ -100,6 +112,119 @@
mSpyAttentionManager);
mSpyAttentionManager.mCurrentAttentionCheck = attentionCheck;
mSpyAttentionManager.mService = new MockIAttentionService();
+ doNothing().when(mSpyAttentionManager).freeIfInactiveLocked();
+ }
+
+ @Test
+ public void testRegisterProximityUpdates_returnFalseWhenServiceDisabled() {
+ mSpyAttentionManager.mIsServiceEnabled = false;
+
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ .isFalse();
+ }
+
+ @Test
+ public void testRegisterProximityUpdates_returnFalseWhenServiceUnavailable() {
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(false).when(mSpyAttentionManager).isServiceAvailable();
+
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ .isFalse();
+ }
+
+ @Test
+ public void testRegisterProximityUpdates_returnFalseWhenPowerManagerNotInteract()
+ throws RemoteException {
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+ doReturn(false).when(mMockIPowerManager).isInteractive();
+
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ .isFalse();
+ }
+
+ @Test
+ public void testRegisterProximityUpdates_callOnSuccess() throws RemoteException {
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+ doReturn(true).when(mMockIPowerManager).isInteractive();
+
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ .isTrue();
+ verify(mMockProximityCallbackInternal, times(1))
+ .onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+ }
+
+ @Test
+ public void testRegisterProximityUpdates_callOnSuccessTwiceInARow() throws RemoteException {
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+ doReturn(true).when(mMockIPowerManager).isInteractive();
+
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ .isTrue();
+
+ ProximityUpdate prevProximityUpdate = mSpyAttentionManager.mCurrentProximityUpdate;
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ .isTrue();
+ assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isEqualTo(prevProximityUpdate);
+ verify(mMockProximityCallbackInternal, times(1))
+ .onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+ }
+
+ @Test
+ public void testUnregisterProximityUpdates_noCrashWhenNoCallbackIsRegistered() {
+ mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
+ verifyZeroInteractions(mMockProximityCallbackInternal);
+ }
+
+ @Test
+ public void testUnregisterProximityUpdates_noCrashWhenCallbackMismatched()
+ throws RemoteException {
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+ doReturn(true).when(mMockIPowerManager).isInteractive();
+ mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal);
+ verify(mMockProximityCallbackInternal, times(1))
+ .onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+
+ ProximityCallbackInternal mismatchedCallback = new ProximityCallbackInternal() {
+ @Override
+ public void onProximityUpdate(double distance) {
+ fail("Callback shouldn't have responded.");
+ }
+ };
+ mSpyAttentionManager.onStopProximityUpdates(mismatchedCallback);
+
+ verifyNoMoreInteractions(mMockProximityCallbackInternal);
+ }
+
+ @Test
+ public void testUnregisterProximityUpdates_cancelRegistrationWhenMatched()
+ throws RemoteException {
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+ doReturn(true).when(mMockIPowerManager).isInteractive();
+ mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal);
+ mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
+
+ assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isNull();
+ }
+
+ @Test
+ public void testUnregisterProximityUpdates_noCrashWhenTwiceInARow() throws RemoteException {
+ // Attention Service registers proximity updates.
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+ doReturn(true).when(mMockIPowerManager).isInteractive();
+ mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal);
+ verify(mMockProximityCallbackInternal, times(1))
+ .onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+
+ // Attention Service unregisters the proximity update twice in a row.
+ mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
+ mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
+ verifyNoMoreInteractions(mMockProximityCallbackInternal);
}
@Test
@@ -127,7 +252,6 @@
mSpyAttentionManager.mIsServiceEnabled = true;
doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
doReturn(true).when(mMockIPowerManager).isInteractive();
- doNothing().when(mSpyAttentionManager).freeIfInactiveLocked();
mSpyAttentionManager.mCurrentAttentionCheck = null;
AttentionCallbackInternal callback = Mockito.mock(AttentionCallbackInternal.class);
@@ -213,6 +337,13 @@
public void cancelAttentionCheck(IAttentionCallback callback) {
}
+ public void onStartProximityUpdates(IProximityCallback callback) throws RemoteException {
+ callback.onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+ }
+
+ public void onStopProximityUpdates() throws RemoteException {
+ }
+
public IBinder asBinder() {
return null;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
new file mode 100644
index 0000000..5746f6f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.biometrics.log;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.StatusBarManager;
+import android.hardware.biometrics.IBiometricContextListener;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.statusbar.ISessionListener;
+import com.android.internal.statusbar.IStatusBarService;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+@Presubmit
+@SmallTest
+public class BiometricContextProviderTest {
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private IStatusBarService mStatusBarService;
+ @Mock
+ private ISessionListener mSessionListener;
+ @Mock
+ private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+
+ private OperationContext mOpContext = new OperationContext();
+ private IBiometricContextListener mListener;
+ private BiometricContextProvider mProvider;
+
+ @Before
+ public void setup() throws RemoteException {
+ when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+ mProvider = new BiometricContextProvider(mAmbientDisplayConfiguration, mStatusBarService,
+ null /* handler */);
+ ArgumentCaptor<IBiometricContextListener> captor =
+ ArgumentCaptor.forClass(IBiometricContextListener.class);
+ verify(mStatusBarService).setBiometicContextListener(captor.capture());
+ mListener = captor.getValue();
+ ArgumentCaptor<ISessionListener> sessionCaptor =
+ ArgumentCaptor.forClass(ISessionListener.class);
+ verify(mStatusBarService).registerSessionListener(anyInt(), sessionCaptor.capture());
+ mSessionListener = sessionCaptor.getValue();
+ }
+
+ @Test
+ public void testIsAoD() throws RemoteException {
+ mListener.onDozeChanged(true);
+ assertThat(mProvider.isAoD()).isTrue();
+ mListener.onDozeChanged(false);
+ assertThat(mProvider.isAoD()).isFalse();
+
+ when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(false);
+ mListener.onDozeChanged(true);
+ assertThat(mProvider.isAoD()).isFalse();
+ mListener.onDozeChanged(false);
+ assertThat(mProvider.isAoD()).isFalse();
+ }
+
+ @Test
+ public void testSubscribesToAoD() throws RemoteException {
+ final List<Boolean> expected = ImmutableList.of(true, false, true, true, false);
+ final List<Boolean> actual = new ArrayList<>();
+
+ mProvider.subscribe(mOpContext, ctx -> {
+ assertThat(ctx).isSameInstanceAs(mOpContext);
+ actual.add(ctx.isAoD);
+ });
+
+ for (boolean v : expected) {
+ mListener.onDozeChanged(v);
+ }
+
+ assertThat(actual).containsExactlyElementsIn(expected).inOrder();
+ }
+
+ @Test
+ public void testUnsubscribes() throws RemoteException {
+ final Consumer<OperationContext> emptyConsumer = mock(Consumer.class);
+ mProvider.subscribe(mOpContext, emptyConsumer);
+ mProvider.unsubscribe(mOpContext);
+
+ mListener.onDozeChanged(true);
+
+ final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class);
+ mProvider.subscribe(mOpContext, nonEmptyConsumer);
+ mListener.onDozeChanged(false);
+ mProvider.unsubscribe(mOpContext);
+ mListener.onDozeChanged(true);
+
+ verify(emptyConsumer, never()).accept(any());
+ verify(nonEmptyConsumer).accept(same(mOpContext));
+ }
+
+ @Test
+ public void testSessionId() throws RemoteException {
+ final int keyguardSessionId = 10;
+ final int bpSessionId = 20;
+
+ assertThat(mProvider.getBiometricPromptSessionId()).isNull();
+ assertThat(mProvider.getKeyguardEntrySessionId()).isNull();
+
+ mSessionListener.onSessionStarted(StatusBarManager.SESSION_KEYGUARD,
+ InstanceId.fakeInstanceId(keyguardSessionId));
+
+ assertThat(mProvider.getBiometricPromptSessionId()).isNull();
+ assertThat(mProvider.getKeyguardEntrySessionId()).isEqualTo(keyguardSessionId);
+
+ mSessionListener.onSessionStarted(StatusBarManager.SESSION_BIOMETRIC_PROMPT,
+ InstanceId.fakeInstanceId(bpSessionId));
+
+ assertThat(mProvider.getBiometricPromptSessionId()).isEqualTo(bpSessionId);
+ assertThat(mProvider.getKeyguardEntrySessionId()).isEqualTo(keyguardSessionId);
+
+ mSessionListener.onSessionEnded(StatusBarManager.SESSION_KEYGUARD,
+ InstanceId.fakeInstanceId(keyguardSessionId));
+
+ assertThat(mProvider.getBiometricPromptSessionId()).isEqualTo(bpSessionId);
+ assertThat(mProvider.getKeyguardEntrySessionId()).isNull();
+
+ mSessionListener.onSessionEnded(StatusBarManager.SESSION_BIOMETRIC_PROMPT,
+ InstanceId.fakeInstanceId(bpSessionId));
+
+ assertThat(mProvider.getBiometricPromptSessionId()).isNull();
+ assertThat(mProvider.getKeyguardEntrySessionId()).isNull();
+ }
+
+ @Test
+ public void testUpdate() throws RemoteException {
+ mListener.onDozeChanged(false);
+ OperationContext context = mProvider.updateContext(mOpContext, false /* crypto */);
+
+ // default state when nothing has been set
+ assertThat(context).isSameInstanceAs(mOpContext);
+ assertThat(mOpContext.id).isEqualTo(0);
+ assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN);
+ assertThat(mOpContext.isAoD).isEqualTo(false);
+ assertThat(mOpContext.isCrypto).isEqualTo(false);
+
+ for (int type : List.of(StatusBarManager.SESSION_BIOMETRIC_PROMPT,
+ StatusBarManager.SESSION_KEYGUARD)) {
+ final int id = 40 + type;
+ final boolean aod = (type & 1) == 0;
+
+ mListener.onDozeChanged(aod);
+ mSessionListener.onSessionStarted(type, InstanceId.fakeInstanceId(id));
+ context = mProvider.updateContext(mOpContext, false /* crypto */);
+ assertThat(context).isSameInstanceAs(mOpContext);
+ assertThat(mOpContext.id).isEqualTo(id);
+ assertThat(mOpContext.reason).isEqualTo(reason(type));
+ assertThat(mOpContext.isAoD).isEqualTo(aod);
+ assertThat(mOpContext.isCrypto).isEqualTo(false);
+
+ mSessionListener.onSessionEnded(type, InstanceId.fakeInstanceId(id));
+ }
+
+ context = mProvider.updateContext(mOpContext, false /* crypto */);
+ assertThat(context).isSameInstanceAs(mOpContext);
+ assertThat(mOpContext.id).isEqualTo(0);
+ assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN);
+ assertThat(mOpContext.isAoD).isEqualTo(false);
+ assertThat(mOpContext.isCrypto).isEqualTo(false);
+ }
+
+ private static byte reason(int type) {
+ if (type == StatusBarManager.SESSION_BIOMETRIC_PROMPT) {
+ return OperationReason.BIOMETRIC_PROMPT;
+ }
+ if (type == StatusBarManager.SESSION_KEYGUARD) {
+ return OperationReason.KEYGUARD;
+ }
+ return OperationReason.UNKNOWN;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 2b72fab..fe023374 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -31,6 +31,7 @@
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
import android.hardware.input.InputSensorInfo;
import android.platform.test.annotations.Presubmit;
import android.testing.TestableContext;
@@ -44,7 +45,8 @@
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
@Presubmit
@SmallTest
@@ -55,6 +57,9 @@
private static final int DEFAULT_CLIENT = BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT;
@Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Rule
public TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getContext());
@Mock
@@ -64,12 +69,12 @@
@Mock
private BaseClientMonitor mClient;
+ private OperationContext mOpContext;
private BiometricLogger mLogger;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
-
+ mOpContext = new OperationContext();
mContext.addMockSystemService(SensorManager.class, mSensorManager);
when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(
new Sensor(new InputSensorInfo("", "", 0, 0, Sensor.TYPE_LIGHT, 0, 0, 0, 0, 0, 0,
@@ -91,14 +96,13 @@
final int acquiredInfo = 2;
final int vendorCode = 3;
- final boolean isCrypto = true;
final int targetUserId = 9;
- mLogger.logOnAcquired(mContext, acquiredInfo, vendorCode, isCrypto, targetUserId);
+ mLogger.logOnAcquired(mContext, mOpContext, acquiredInfo, vendorCode, targetUserId);
- verify(mSink).acquired(
+ verify(mSink).acquired(eq(mOpContext),
eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
- eq(acquiredInfo), eq(vendorCode), eq(isCrypto), eq(targetUserId));
+ eq(acquiredInfo), eq(vendorCode), eq(targetUserId));
}
@Test
@@ -107,17 +111,16 @@
final boolean authenticated = true;
final boolean requireConfirmation = false;
- final boolean isCrypto = false;
final int targetUserId = 11;
final boolean isBiometricPrompt = true;
- mLogger.logOnAuthenticated(mContext,
- authenticated, requireConfirmation, isCrypto, targetUserId, isBiometricPrompt);
+ mLogger.logOnAuthenticated(mContext, mOpContext,
+ authenticated, requireConfirmation, targetUserId, isBiometricPrompt);
- verify(mSink).authenticate(
+ verify(mSink).authenticate(eq(mOpContext),
eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
- anyLong(), eq(authenticated), anyInt(), eq(requireConfirmation), eq(isCrypto),
- eq(targetUserId), eq(isBiometricPrompt), anyFloat());
+ anyLong(), anyInt(), eq(requireConfirmation),
+ eq(targetUserId), anyFloat());
}
@Test
@@ -141,14 +144,13 @@
final int error = 7;
final int vendorCode = 11;
- final boolean isCrypto = false;
final int targetUserId = 9;
- mLogger.logOnError(mContext, error, vendorCode, isCrypto, targetUserId);
+ mLogger.logOnError(mContext, mOpContext, error, vendorCode, targetUserId);
- verify(mSink).error(
+ verify(mSink).error(eq(mOpContext),
eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
- anyLong(), eq(error), eq(vendorCode), eq(isCrypto), eq(targetUserId));
+ anyLong(), eq(error), eq(vendorCode), eq(targetUserId));
}
@Test
@@ -173,38 +175,34 @@
private void testDisabledMetrics(boolean isBadConfig) {
mLogger.disableMetrics();
- mLogger.logOnAcquired(mContext,
+ mLogger.logOnAcquired(mContext, mOpContext,
0 /* acquiredInfo */,
1 /* vendorCode */,
- true /* isCrypto */,
8 /* targetUserId */);
- mLogger.logOnAuthenticated(mContext,
+ mLogger.logOnAuthenticated(mContext, mOpContext,
true /* authenticated */,
true /* requireConfirmation */,
- false /* isCrypto */,
4 /* targetUserId */,
true/* isBiometricPrompt */);
mLogger.logOnEnrolled(2 /* targetUserId */,
10 /* latency */,
true /* enrollSuccessful */);
- mLogger.logOnError(mContext,
+ mLogger.logOnError(mContext, mOpContext,
4 /* error */,
0 /* vendorCode */,
- false /* isCrypto */,
6 /* targetUserId */);
- verify(mSink, never()).acquired(
+ verify(mSink, never()).acquired(eq(mOpContext),
anyInt(), anyInt(), anyInt(), anyBoolean(),
- anyInt(), anyInt(), anyBoolean(), anyInt());
- verify(mSink, never()).authenticate(
+ anyInt(), anyInt(), anyInt());
+ verify(mSink, never()).authenticate(eq(mOpContext),
anyInt(), anyInt(), anyInt(), anyBoolean(),
- anyLong(), anyBoolean(), anyInt(), anyBoolean(),
- anyBoolean(), anyInt(), anyBoolean(), anyFloat());
+ anyLong(), anyInt(), anyBoolean(), anyInt(), anyFloat());
verify(mSink, never()).enroll(
anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyBoolean(), anyFloat());
- verify(mSink, never()).error(
+ verify(mSink, never()).error(eq(mOpContext),
anyInt(), anyInt(), anyInt(), anyBoolean(),
- anyLong(), anyInt(), anyInt(), anyBoolean(), anyInt());
+ anyLong(), anyInt(), anyInt(), anyInt());
mLogger.logUnknownEnrollmentInFramework();
mLogger.logUnknownEnrollmentInHal();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
index 9bb722f..2d9d868 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
@@ -22,6 +22,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -34,6 +35,9 @@
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
@@ -92,9 +96,8 @@
@NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter callback) {
super(context, lazyDaemon, token, callback, 0 /* userId */, "Test", 0 /* cookie */,
- TEST_SENSOR_ID /* sensorId */, true /* shouldVibrate */, 0 /* statsModality */,
- 0 /* statsAction */,
- 0 /* statsClient */);
+ TEST_SENSOR_ID /* sensorId */, true /* shouldVibrate */,
+ mock(BiometricLogger.class), mock(BiometricContext.class));
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
index 51d234d..8e6d90c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
@@ -30,6 +30,7 @@
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import org.junit.Before;
@@ -49,6 +50,8 @@
@Mock
private BiometricLogger mLogger;
@Mock
+ private BiometricContext mBiometricContext;
+ @Mock
private ClientMonitorCallback mCallback;
private TestClientMonitor mClientMonitor;
@@ -109,7 +112,7 @@
TestClientMonitor() {
super(mContext, mToken, mListener, 9 /* userId */, "foo" /* owner */, 2 /* cookie */,
- 5 /* sensorId */, mLogger);
+ 5 /* sensorId */, mLogger, mBiometricContext);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index 8751cf3..64be569 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -36,6 +36,9 @@
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,7 +57,8 @@
public abstract static class InterruptableMonitor<T>
extends HalClientMonitor<T> implements Interruptable {
public InterruptableMonitor() {
- super(null, null, null, null, 0, null, 0, 0, 0, 0, 0);
+ super(null, null, null, null, 0, null, 0, 0,
+ mock(BiometricLogger.class), mock(BiometricContext.class));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index ecd9abc..0fa2b41 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -51,6 +51,8 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.nano.BiometricSchedulerProto;
import com.android.server.biometrics.nano.BiometricsProto;
@@ -526,10 +528,10 @@
@NonNull ClientMonitorCallbackConverter listener) {
super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */,
- TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */,
- 0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class),
- false /* isKeyguard */, true /* shouldVibrate */,
- false /* isKeyguardBypassEnabled */);
+ TEST_SENSOR_ID, mock(BiometricLogger.class), mock(BiometricContext.class),
+ true /* isStrongBiometric */, null /* taskStackListener */,
+ mock(LockoutTracker.class), false /* isKeyguard */,
+ true /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
}
@Override
@@ -573,8 +575,9 @@
@NonNull ClientMonitorCallbackConverter listener) {
super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
"test" /* owner */, mock(BiometricUtils.class),
- 5 /* timeoutSec */, 0 /* statsModality */, TEST_SENSOR_ID,
- true /* shouldVibrate */);
+ 5 /* timeoutSec */, TEST_SENSOR_ID,
+ true /* shouldVibrate */,
+ mock(BiometricLogger.class), mock(BiometricContext.class));
}
@Override
@@ -613,8 +616,8 @@
TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
@NonNull Supplier<Object> lazyDaemon, int cookie, int protoEnum) {
super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
- TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */,
- 0 /* statsAction */, 0 /* statsClient */);
+ TAG, cookie, TEST_SENSOR_ID,
+ mock(BiometricLogger.class), mock(BiometricContext.class));
mProtoEnum = protoEnum;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
index 587bb60..092ca19 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
@@ -64,7 +64,7 @@
ClientMonitorCompositeCallback callback = new ClientMonitorCompositeCallback(callbacks);
callback.onClientStarted(mClientMonitor);
- final InOrder order = inOrder(expected);
+ final InOrder order = inOrder((Object[]) expected);
for (ClientMonitorCallback cb : expected) {
order.verify(cb).onClientStarted(eq(mClientMonitor));
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
index 30777cd..52eee9a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -41,6 +41,9 @@
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,6 +69,10 @@
private Context mContext;
@Mock
private IBiometricService mBiometricService;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
private TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback();
private TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback();
@@ -88,7 +95,8 @@
@Override
public StopUserClient<?> getStopUserClient(int userId) {
return new TestStopUserClient(mContext, Object::new, mToken, userId,
- TEST_SENSOR_ID, mUserStoppedCallback);
+ TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+ mUserStoppedCallback);
}
@NonNull
@@ -96,7 +104,8 @@
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
mStartUserClientCount++;
return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
- TEST_SENSOR_ID, mUserStartedCallback, mStartOperationsFinish);
+ TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+ mUserStartedCallback, mStartOperationsFinish);
}
},
CoexCoordinator.getInstance());
@@ -226,8 +235,10 @@
private static class TestStopUserClient extends StopUserClient<Object> {
public TestStopUserClient(@NonNull Context context,
@NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
- int sensorId, @NonNull UserStoppedCallback callback) {
- super(context, lazyDaemon, token, userId, sensorId, callback);
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext,
+ @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
}
@Override
@@ -254,8 +265,10 @@
public TestStartUserClient(@NonNull Context context,
@NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
- int sensorId, @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) {
- super(context, lazyDaemon, token, userId, sensorId, callback);
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext,
+ @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) {
+ super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
mShouldFinish = shouldFinish;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
new file mode 100644
index 0000000..aba93b0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutCache;
+import com.android.server.biometrics.sensors.face.UsageStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceAuthenticationClientTest {
+
+ private static final int USER_ID = 12;
+ private static final long OP_ID = 32;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private LockoutCache mLockoutCache;
+ @Mock
+ private UsageStats mUsageStats;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Captor
+ private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Before
+ public void setup() {
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void authNoContext_v1() throws RemoteException {
+ final FaceAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+
+ verify(mHal).authenticate(eq(OP_ID));
+ verify(mHal, never()).authenticateWithContext(anyLong(), any());
+ }
+
+ @Test
+ public void authWithContext_v2() throws RemoteException {
+ final FaceAuthenticationClient client = createClient(2);
+ client.start(mCallback);
+
+ InOrder order = inOrder(mHal, mBiometricContext);
+ order.verify(mBiometricContext).updateContext(
+ mOperationContextCaptor.capture(), anyBoolean());
+ order.verify(mHal).authenticateWithContext(
+ eq(OP_ID), same(mOperationContextCaptor.getValue()));
+ verify(mHal, never()).authenticate(anyLong());
+ }
+
+ private FaceAuthenticationClient createClient(int version) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FaceAuthenticationClient(mContext, () -> aidl, mToken,
+ 2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
+ false /* restricted */, "test-owner", 4 /* cookie */,
+ false /* requireConfirmation */, 9 /* sensorId */,
+ mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
+ mUsageStats, mLockoutCache, false /* allowBackgroundAuthentication */,
+ false /* isKeyguardBypassEnabled */, null /* sensorPrivacyManager */);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
new file mode 100644
index 0000000..25135c6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceDetectClientTest {
+
+ private static final int USER_ID = 12;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Captor
+ private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Before
+ public void setup() {
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void detectNoContext_v1() throws RemoteException {
+ final FaceDetectClient client = createClient(1);
+ client.start(mCallback);
+
+ verify(mHal).detectInteraction();
+ verify(mHal, never()).detectInteractionWithContext(any());
+ }
+
+ @Test
+ public void detectWithContext_v2() throws RemoteException {
+ final FaceDetectClient client = createClient(2);
+ client.start(mCallback);
+
+ InOrder order = inOrder(mHal, mBiometricContext);
+ order.verify(mBiometricContext).updateContext(
+ mOperationContextCaptor.capture(), anyBoolean());
+ order.verify(mHal).detectInteractionWithContext(same(mOperationContextCaptor.getValue()));
+ verify(mHal, never()).detectInteraction();
+ }
+
+ private FaceDetectClient createClient(int version) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FaceDetectClient(mContext, () -> aidl, mToken,
+ 99 /* requestId */, mClientMonitorCallbackConverter, USER_ID,
+ "own-it", 5 /* sensorId */, mBiometricLogger, mBiometricContext,
+ false /* isStrongBiometric */, null /* sensorPrivacyManager */);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
new file mode 100644
index 0000000..38e048b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyByte;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceEnrollClientTest {
+
+ private static final byte[] HAT = new byte[69];
+ private static final int USER_ID = 12;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricUtils<Face> mUtils;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Captor
+ private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Before
+ public void setup() {
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void enrollNoContext_v1() throws RemoteException {
+ final FaceEnrollClient client = createClient(1);
+ client.start(mCallback);
+
+ verify(mHal).enroll(any(), anyByte(), any(), any());
+ verify(mHal, never()).enrollWithContext(any(), anyByte(), any(), any(), any());
+ }
+
+ @Test
+ public void enrollWithContext_v2() throws RemoteException {
+ final FaceEnrollClient client = createClient(2);
+ client.start(mCallback);
+
+ InOrder order = inOrder(mHal, mBiometricContext);
+ order.verify(mBiometricContext).updateContext(
+ mOperationContextCaptor.capture(), anyBoolean());
+ order.verify(mHal).enrollWithContext(any(), anyByte(), any(), any(),
+ same(mOperationContextCaptor.getValue()));
+ verify(mHal, never()).enroll(any(), anyByte(), any(), any());
+ }
+
+ private FaceEnrollClient createClient(int version) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FaceEnrollClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter,
+ USER_ID, HAT, "com.foo.bar", 44 /* requestId */,
+ mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */,
+ null /* previewSurface */, 8 /* sensorId */,
+ mBiometricLogger, mBiometricContext, 5 /* maxTemplatesPerUser */,
+ true /* debugConsent */);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 0ac00aa..12b8264 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -37,6 +37,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -60,6 +61,8 @@
private UserManager mUserManager;
@Mock
private IFace mDaemon;
+ @Mock
+ private BiometricContext mBiometricContext;
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -89,7 +92,7 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
mFaceProvider = new TestableFaceProvider(mDaemon, mContext, mSensorProps, TAG,
- mLockoutResetDispatcher);
+ mLockoutResetDispatcher, mBiometricContext);
}
@SuppressWarnings("rawtypes")
@@ -139,8 +142,9 @@
@NonNull Context context,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
- super(context, props, halInstanceName, lockoutResetDispatcher);
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext) {
+ super(context, props, halInstanceName, lockoutResetDispatcher, biometricContext);
mDaemon = daemon;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 61e4776..b60324e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -32,6 +32,8 @@
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.CoexCoordinator;
import com.android.server.biometrics.sensors.LockoutCache;
@@ -66,6 +68,10 @@
private Sensor.HalSessionCallback.Callback mHalSessionCallback;
@Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -102,7 +108,8 @@
mScheduler.scheduleClientMonitor(new FaceResetLockoutClient(mContext,
() -> new AidlSession(1, mSession, USER_ID, mHalCallback),
- USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache, mLockoutResetDispatcher));
+ USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext,
+ HAT, mLockoutCache, mLockoutResetDispatcher));
mLooper.dispatchAll();
verifyNotLocked();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index 21a7a8a..116d2d5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -41,6 +41,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -70,6 +71,8 @@
private UserManager mUserManager;
@Mock
private BiometricScheduler mScheduler;
+ @Mock
+ private BiometricContext mBiometricContext;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -100,7 +103,8 @@
resetLockoutRequiresChallenge);
Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
- mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler);
+ mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler,
+ mBiometricContext);
mBinder = new Binder();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
index 931fad1..ec08329 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
@@ -34,6 +34,8 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -62,6 +64,10 @@
private IFaceServiceReceiver mOtherReceiver;
@Mock
private ClientMonitorCallback mMonitorCallback;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
private FaceGenerateChallengeClient mClient;
@@ -75,7 +81,7 @@
mClient = new FaceGenerateChallengeClient(mContext, () -> mIBiometricsFace, new Binder(),
new ClientMonitorCallbackConverter(mClientReceiver), USER_ID,
- TAG, SENSOR_ID, START_TIME);
+ TAG, SENSOR_ID, mBiometricLogger, mBiometricContext , START_TIME);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
new file mode 100644
index 0000000..de0f038
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -0,0 +1,288 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.CallbackWithProbe;
+import com.android.server.biometrics.log.Probe;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutCache;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Consumer;
+
+@Presubmit
+@SmallTest
+public class FingerprintAuthenticationClientTest {
+
+ private static final int USER_ID = 8;
+ private static final long OP_ID = 7;
+ private static final int POINTER_ID = 0;
+ private static final int TOUCH_X = 8;
+ private static final int TOUCH_Y = 20;
+ private static final float TOUCH_MAJOR = 4.4f;
+ private static final float TOUCH_MINOR = 5.5f;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private LockoutCache mLockoutCache;
+ @Mock
+ private IUdfpsOverlayController mUdfpsOverlayController;
+ @Mock
+ private ISidefpsController mSideFpsController;
+ @Mock
+ private FingerprintSensorPropertiesInternal mSensorProps;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Mock
+ private Probe mLuxProbe;
+ @Captor
+ private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+ @Captor
+ private ArgumentCaptor<PointerContext> mPointerContextCaptor;
+ @Captor
+ private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Before
+ public void setup() {
+ when(mBiometricLogger.createALSCallback(anyBoolean())).thenAnswer(i ->
+ new CallbackWithProbe<>(mLuxProbe, i.getArgument(0)));
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void authNoContext_v1() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+
+ verify(mHal).authenticate(eq(OP_ID));
+ verify(mHal, never()).authenticateWithContext(anyLong(), any());
+ }
+
+ @Test
+ public void authWithContext_v2() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(2);
+ client.start(mCallback);
+
+ InOrder order = inOrder(mHal, mBiometricContext);
+ order.verify(mBiometricContext).updateContext(
+ mOperationContextCaptor.capture(), anyBoolean());
+ order.verify(mHal).authenticateWithContext(
+ eq(OP_ID), same(mOperationContextCaptor.getValue()));
+ verify(mHal, never()).authenticate(anyLong());
+ }
+
+ @Test
+ public void pointerUp_v1() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ client.onPointerUp();
+
+ verify(mHal).onPointerUp(eq(POINTER_ID));
+ verify(mHal, never()).onPointerUpWithContext(any());
+ }
+
+ @Test
+ public void pointerDown_v1() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+ verify(mHal).onPointerDown(eq(0),
+ eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR));
+ verify(mHal, never()).onPointerDownWithContext(any());
+ }
+
+ @Test
+ public void pointerUpWithContext_v2() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(2);
+ client.start(mCallback);
+ client.onPointerUp();
+
+ verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture());
+ verify(mHal, never()).onPointerUp(eq(POINTER_ID));
+
+ final PointerContext pContext = mPointerContextCaptor.getValue();
+ assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+ }
+
+ @Test
+ public void pointerDownWithContext_v2() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(2);
+ client.start(mCallback);
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+ verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture());
+ verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat());
+
+ final PointerContext pContext = mPointerContextCaptor.getValue();
+ assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+ }
+
+ @Test
+ public void luxProbeWhenFingerDown() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+ verify(mLuxProbe).enable();
+
+ client.onAcquired(2, 0);
+ verify(mLuxProbe, never()).disable();
+
+ client.onPointerUp();
+ verify(mLuxProbe).disable();
+
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+ verify(mLuxProbe, times(2)).enable();
+ }
+
+ @Test
+ public void notifyHalWhenContextChanges() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+
+ verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture());
+ OperationContext opContext = mOperationContextCaptor.getValue();
+
+ // fake an update to the context
+ verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture());
+ mContextInjector.getValue().accept(opContext);
+ verify(mHal).onContextChanged(eq(opContext));
+
+ client.stopHalOperation();
+ verify(mBiometricContext).unsubscribe(same(opContext));
+ }
+
+ @Test
+ public void showHideOverlay_cancel() throws RemoteException {
+ showHideOverlay(c -> c.cancel());
+ }
+
+ @Test
+ public void showHideOverlay_stop() throws RemoteException {
+ showHideOverlay(c -> c.stopHalOperation());
+ }
+
+ @Test
+ public void showHideOverlay_error() throws RemoteException {
+ showHideOverlay(c -> c.onError(0, 0));
+ verify(mCallback).onClientFinished(any(), eq(false));
+ }
+
+ @Test
+ public void showHideOverlay_lockout() throws RemoteException {
+ showHideOverlay(c -> c.onLockoutTimed(5000));
+ }
+
+ @Test
+ public void showHideOverlay_lockoutPerm() throws RemoteException {
+ showHideOverlay(c -> c.onLockoutPermanent());
+ }
+
+ private void showHideOverlay(Consumer<FingerprintAuthenticationClient> block)
+ throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient();
+
+ client.start(mCallback);
+
+ verify(mUdfpsOverlayController).showUdfpsOverlay(anyInt(), anyInt(), any());
+ verify(mSideFpsController).show(anyInt(), anyInt());
+
+ block.accept(client);
+
+ verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
+ verify(mSideFpsController).hide(anyInt());
+ }
+
+ private FingerprintAuthenticationClient createClient() throws RemoteException {
+ return createClient(100);
+ }
+
+ private FingerprintAuthenticationClient createClient(int version) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken,
+ 2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
+ false /* restricted */, "test-owner", 4 /* cookie */, false /* requireConfirmation */,
+ 9 /* sensorId */, mBiometricLogger, mBiometricContext,
+ true /* isStrongBiometric */,
+ null /* taskStackListener */, mLockoutCache,
+ mUdfpsOverlayController, mSideFpsController,
+ false /* allowBackgroundAuthentication */, mSensorProps);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
new file mode 100644
index 0000000..93cbef1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FingerprintDetectClientTest {
+
+ private static final int USER_ID = 8;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private IUdfpsOverlayController mUdfpsOverlayController;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Captor
+ private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Before
+ public void setup() {
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void detectNoContext_v1() throws RemoteException {
+ final FingerprintDetectClient client = createClient(1);
+
+ client.start(mCallback);
+
+ verify(mHal).detectInteraction();
+ verify(mHal, never()).detectInteractionWithContext(any());
+ }
+
+ @Test
+ public void detectNoContext_v2() throws RemoteException {
+ final FingerprintDetectClient client = createClient(2);
+
+ client.start(mCallback);
+
+ InOrder order = inOrder(mHal, mBiometricContext);
+ order.verify(mBiometricContext).updateContext(
+ mOperationContextCaptor.capture(), anyBoolean());
+ order.verify(mHal).detectInteractionWithContext(same(mOperationContextCaptor.getValue()));
+ verify(mHal, never()).detectInteraction();
+ }
+
+ private FingerprintDetectClient createClient(int version) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FingerprintDetectClient(mContext, () -> aidl, mToken,
+ 6 /* requestId */, mClientMonitorCallbackConverter, 2 /* userId */,
+ "a-test", 1 /* sensorId */, mBiometricLogger, mBiometricContext,
+ mUdfpsOverlayController, true /* isStrongBiometric */);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
new file mode 100644
index 0000000..5a96f5c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -0,0 +1,282 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.CallbackWithProbe;
+import com.android.server.biometrics.log.Probe;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Consumer;
+
+@Presubmit
+@SmallTest
+public class FingerprintEnrollClientTest {
+
+ private static final byte[] HAT = new byte[69];
+ private static final int USER_ID = 8;
+ private static final int POINTER_ID = 0;
+ private static final int TOUCH_X = 8;
+ private static final int TOUCH_Y = 20;
+ private static final float TOUCH_MAJOR = 4.4f;
+ private static final float TOUCH_MINOR = 5.5f;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricUtils<Fingerprint> mBiometricUtils;
+ @Mock
+ private IUdfpsOverlayController mUdfpsOverlayController;
+ @Mock
+ private ISidefpsController mSideFpsController;
+ @Mock
+ private FingerprintSensorPropertiesInternal mSensorProps;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Mock
+ private Probe mLuxProbe;
+ @Captor
+ private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+ @Captor
+ private ArgumentCaptor<PointerContext> mPointerContextCaptor;
+ @Captor
+ private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Before
+ public void setup() {
+ when(mBiometricLogger.createALSCallback(anyBoolean())).thenAnswer(i ->
+ new CallbackWithProbe<>(mLuxProbe, i.getArgument(0)));
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void enrollNoContext_v1() throws RemoteException {
+ final FingerprintEnrollClient client = createClient(1);
+
+ client.start(mCallback);
+
+ verify(mHal).enroll(any());
+ verify(mHal, never()).enrollWithContext(any(), any());
+ }
+
+ @Test
+ public void enrollWithContext_v2() throws RemoteException {
+ final FingerprintEnrollClient client = createClient(2);
+
+ client.start(mCallback);
+
+ InOrder order = inOrder(mHal, mBiometricContext);
+ order.verify(mBiometricContext).updateContext(
+ mOperationContextCaptor.capture(), anyBoolean());
+ order.verify(mHal).enrollWithContext(any(), same(mOperationContextCaptor.getValue()));
+ verify(mHal, never()).enroll(any());
+ }
+
+ @Test
+ public void pointerUp_v1() throws RemoteException {
+ final FingerprintEnrollClient client = createClient(1);
+ client.start(mCallback);
+ client.onPointerUp();
+
+ verify(mHal).onPointerUp(eq(POINTER_ID));
+ verify(mHal, never()).onPointerUpWithContext(any());
+ }
+
+ @Test
+ public void pointerDown_v1() throws RemoteException {
+ final FingerprintEnrollClient client = createClient(1);
+ client.start(mCallback);
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+ verify(mHal).onPointerDown(eq(0),
+ eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR));
+ verify(mHal, never()).onPointerDownWithContext(any());
+ }
+
+ @Test
+ public void pointerUpWithContext_v2() throws RemoteException {
+ final FingerprintEnrollClient client = createClient(2);
+ client.start(mCallback);
+ client.onPointerUp();
+
+ verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture());
+ verify(mHal, never()).onPointerUp(eq(POINTER_ID));
+
+ final PointerContext pContext = mPointerContextCaptor.getValue();
+ assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+ }
+
+ @Test
+ public void pointerDownWithContext_v2() throws RemoteException {
+ final FingerprintEnrollClient client = createClient(2);
+ client.start(mCallback);
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+ verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture());
+ verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat());
+
+ final PointerContext pContext = mPointerContextCaptor.getValue();
+ assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+ }
+
+ @Test
+ public void luxProbeWhenFingerDown() throws RemoteException {
+ final FingerprintEnrollClient client = createClient();
+ client.start(mCallback);
+
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+ verify(mLuxProbe).enable();
+
+ client.onAcquired(2, 0);
+ verify(mLuxProbe, never()).disable();
+
+ client.onPointerUp();
+ verify(mLuxProbe).disable();
+
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+ verify(mLuxProbe, times(2)).enable();
+ }
+
+ @Test
+ public void notifyHalWhenContextChanges() throws RemoteException {
+ final FingerprintEnrollClient client = createClient();
+ client.start(mCallback);
+
+ verify(mHal).enrollWithContext(any(), mOperationContextCaptor.capture());
+ OperationContext opContext = mOperationContextCaptor.getValue();
+
+ // fake an update to the context
+ verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture());
+ mContextInjector.getValue().accept(opContext);
+ verify(mHal).onContextChanged(eq(opContext));
+
+ client.stopHalOperation();
+ verify(mBiometricContext).unsubscribe(same(opContext));
+ }
+
+ @Test
+ public void showHideOverlay_cancel() throws RemoteException {
+ showHideOverlay(c -> c.cancel());
+ }
+
+ @Test
+ public void showHideOverlay_stop() throws RemoteException {
+ showHideOverlay(c -> c.stopHalOperation());
+ }
+
+ @Test
+ public void showHideOverlay_error() throws RemoteException {
+ showHideOverlay(c -> c.onError(0, 0));
+ verify(mCallback).onClientFinished(any(), eq(false));
+ }
+
+ @Test
+ public void showHideOverlay_result() throws RemoteException {
+ showHideOverlay(c -> c.onEnrollResult(new Fingerprint("", 1, 1), 0));
+ }
+
+ private void showHideOverlay(Consumer<FingerprintEnrollClient> block)
+ throws RemoteException {
+ final FingerprintEnrollClient client = createClient();
+
+ client.start(mCallback);
+
+ verify(mUdfpsOverlayController).showUdfpsOverlay(anyInt(), anyInt(), any());
+ verify(mSideFpsController).show(anyInt(), anyInt());
+
+ block.accept(client);
+
+ verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
+ verify(mSideFpsController).hide(anyInt());
+ }
+
+ private FingerprintEnrollClient createClient() throws RemoteException {
+ return createClient(500);
+ }
+
+ private FingerprintEnrollClient createClient(int version) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FingerprintEnrollClient(mContext, () -> aidl, mToken, 6 /* requestId */,
+ mClientMonitorCallbackConverter, 0 /* userId */,
+ HAT, "owner", mBiometricUtils, 8 /* sensorId */,
+ mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController,
+ mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 73f1516..5a1a02e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -40,6 +40,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -71,6 +72,8 @@
private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@Mock
private FingerprintStateCallback mFingerprintStateCallback;
+ @Mock
+ private BiometricContext mBiometricContext;
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -105,7 +108,7 @@
mFingerprintProvider = new TestableFingerprintProvider(mDaemon, mContext,
mFingerprintStateCallback, mSensorProps, TAG, mLockoutResetDispatcher,
- mGestureAvailabilityDispatcher);
+ mGestureAvailabilityDispatcher, mBiometricContext);
}
@SuppressWarnings("rawtypes")
@@ -157,9 +160,10 @@
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext) {
super(context, fingerprintStateCallback, props, halInstanceName, lockoutResetDispatcher,
- gestureAvailabilityDispatcher);
+ gestureAvailabilityDispatcher, biometricContext);
mDaemon = daemon;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 8b7b484..e1a4a2d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -32,6 +32,8 @@
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.CoexCoordinator;
import com.android.server.biometrics.sensors.LockoutCache;
@@ -66,6 +68,10 @@
private Sensor.HalSessionCallback.Callback mHalSessionCallback;
@Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
+ @Mock
+ private BiometricLogger mLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -102,7 +108,8 @@
mScheduler.scheduleClientMonitor(new FingerprintResetLockoutClient(mContext,
() -> new AidlSession(1, mSession, USER_ID, mHalCallback),
- USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache, mLockoutResetDispatcher));
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT, mLockoutCache,
+ mLockoutResetDispatcher));
mLooper.dispatchAll();
verifyNotLocked();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
index f6b9209..529f994 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
@@ -39,6 +39,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
@@ -70,6 +71,8 @@
private BiometricScheduler mScheduler;
@Mock
private FingerprintStateCallback mFingerprintStateCallback;
+ @Mock
+ private BiometricContext mBiometricContext;
private LockoutResetDispatcher mLockoutResetDispatcher;
private Fingerprint21 mFingerprint21;
@@ -101,7 +104,7 @@
mFingerprint21 = new TestableFingerprint21(mContext, mFingerprintStateCallback, sensorProps,
mScheduler, new Handler(Looper.getMainLooper()), mLockoutResetDispatcher,
- mHalResultController);
+ mHalResultController, mBiometricContext);
}
@Test
@@ -126,9 +129,10 @@
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull BiometricScheduler scheduler, @NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull HalResultController controller) {
+ @NonNull HalResultController controller,
+ @NonNull BiometricContext biometricContext) {
super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
- lockoutResetDispatcher, controller);
+ lockoutResetDispatcher, controller, biometricContext);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index ff1b6f6..83fa7ac 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -17,17 +17,23 @@
package com.android.server.companion.virtual;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInputConstants;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
+import android.view.DisplayInfo;
import androidx.test.runner.AndroidJUnit4;
@@ -46,18 +52,31 @@
@Mock
private InputManagerInternal mInputManagerInternalMock;
@Mock
+ private DisplayManagerInternal mDisplayManagerInternalMock;
+ @Mock
private InputController.NativeWrapper mNativeWrapperMock;
+ @Mock
+ private IInputManager mIInputManagerMock;
private InputController mInputController;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.uniqueId = "uniqueId";
+ doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+ LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+ LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
+ InputManager.resetInstance(mIInputManagerMock);
+ doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
+ doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
mInputController = new InputController(new Object(), mNativeWrapperMock);
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index e36263e..33540c8 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -57,6 +58,7 @@
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.DisplayInfo;
import android.view.KeyEvent;
import androidx.test.InstrumentationRegistry;
@@ -80,6 +82,8 @@
private static final int DISPLAY_ID = 2;
private static final int PRODUCT_ID = 10;
private static final int VENDOR_ID = 5;
+ private static final String UNIQUE_ID = "uniqueid";
+ private static final String PHYS = "phys";
private static final int HEIGHT = 1800;
private static final int WIDTH = 900;
private static final Binder BINDER = new Binder("binder");
@@ -116,6 +120,12 @@
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.uniqueId = UNIQUE_ID;
+ doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+ LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+ LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
doNothing().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
@@ -150,11 +160,11 @@
mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
- nullable(String.class), anyInt());
+ nullable(String.class), anyInt(), eq(null));
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
- nullable(String.class), eq(displayId));
+ nullable(String.class), eq(displayId), eq(null));
}
@Test
@@ -167,7 +177,7 @@
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
- nullable(String.class), eq(displayId));
+ nullable(String.class), eq(displayId), eq(null));
}
@Test
@@ -186,7 +196,7 @@
verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
- nullable(String.class), eq(displayId));
+ nullable(String.class), eq(displayId), eq(null));
IBinder wakeLock = wakeLockCaptor.getValue();
mDeviceImpl.onVirtualDisplayRemovedLocked(displayId);
@@ -202,7 +212,7 @@
verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
- nullable(String.class), eq(displayId));
+ nullable(String.class), eq(displayId), eq(null));
IBinder wakeLock = wakeLockCaptor.getValue();
// Close the VirtualDevice without first notifying it of the VirtualDisplay removal.
@@ -274,7 +284,8 @@
BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
- verify(mNativeWrapperMock).openUinputKeyboard(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+ verify(mNativeWrapperMock).openUinputKeyboard(eq(DEVICE_NAME), eq(VENDOR_ID),
+ eq(PRODUCT_ID), anyString());
}
@Test
@@ -284,7 +295,8 @@
BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
- verify(mNativeWrapperMock).openUinputMouse(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+ verify(mNativeWrapperMock).openUinputMouse(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID),
+ anyString());
}
@Test
@@ -294,8 +306,8 @@
BINDER, new Point(WIDTH, HEIGHT));
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
- verify(mNativeWrapperMock).openUinputTouchscreen(DEVICE_NAME, VENDOR_ID, PRODUCT_ID, HEIGHT,
- WIDTH);
+ verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
+ eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
}
@Test
@@ -315,7 +327,7 @@
final int action = VirtualKeyEvent.ACTION_UP;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode)
.setAction(action).build());
verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action);
@@ -340,7 +352,7 @@
final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mInputController.mActivePointerDisplayId = 1;
mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
.setButtonCode(buttonCode)
@@ -355,7 +367,7 @@
final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
assertThrows(
IllegalStateException.class,
() ->
@@ -381,7 +393,7 @@
final float y = 0.7f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mInputController.mActivePointerDisplayId = 1;
mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder()
.setRelativeX(x).setRelativeY(y).build());
@@ -395,7 +407,7 @@
final float y = 0.7f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
assertThrows(
IllegalStateException.class,
() ->
@@ -422,7 +434,7 @@
final float y = 1f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mInputController.mActivePointerDisplayId = 1;
mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
.setXAxisMovement(x)
@@ -437,7 +449,7 @@
final float y = 1f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
assertThrows(
IllegalStateException.class,
() ->
@@ -470,7 +482,7 @@
final int action = VirtualTouchEvent.ACTION_UP;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
.setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build());
verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,
@@ -489,7 +501,7 @@
final float majorAxisSize = 10.0f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
.setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType)
.setPressure(pressure).setMajorAxisSize(majorAxisSize).build());
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 564c4e4..c877bd1 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -28,6 +28,14 @@
import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
@@ -101,6 +109,7 @@
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -138,6 +147,9 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener;
+import com.android.server.pm.RestrictionsSet;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserRestrictionsUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
@@ -7725,30 +7737,20 @@
dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
- int returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1);
- assertThat(dpms.mOwners.hasDeviceOwner()).isTrue();
- assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
-
+ assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
initializeDpms();
-
- returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1);
- assertThat(dpms.mOwners.hasDeviceOwner()).isTrue();
- assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
+ assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
}
@Test
- public void testSetDeviceOwnerType_asDeviceOwner_throwsExceptionWhenSetDeviceOwnerTypeAgain()
+ public void testSetDeviceOwnerType_asDeviceOwner_setDeviceOwnerTypeTwice_success()
throws Exception {
setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_DEFAULT);
dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
- int returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1);
- assertThat(dpms.mOwners.hasDeviceOwner()).isTrue();
- assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
-
- assertThrows(IllegalStateException.class,
- () -> dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_DEFAULT));
+ assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
}
@Test
@@ -7764,6 +7766,296 @@
}
@Test
+ public void testSetUserRestriction_financeDo_invalidRestrictions_restrictionNotSet()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ for (String restriction : UserRestrictionsUtils.USER_RESTRICTIONS) {
+ if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(restriction)) {
+ assertNoDeviceOwnerRestrictions();
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.addUserRestriction(admin1, restriction));
+
+ verify(getServices().userManagerInternal, never())
+ .setDevicePolicyUserRestrictions(anyInt(), any(), any(), anyBoolean());
+ }
+ }
+ }
+
+ @Test
+ public void testSetUserRestriction_financeDo_validRestrictions_setsRestriction()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ for (String restriction : UserRestrictionsUtils.USER_RESTRICTIONS) {
+ if (UserRestrictionsUtils.canFinancedDeviceOwnerChange(restriction)) {
+ assertNoDeviceOwnerRestrictions();
+ dpm.addUserRestriction(admin1, restriction);
+
+ Bundle globalRestrictions =
+ dpms.getDeviceOwnerAdminLocked().getGlobalUserRestrictions(
+ UserManagerInternal.OWNER_TYPE_DEVICE_OWNER);
+ RestrictionsSet localRestrictions = new RestrictionsSet();
+ localRestrictions.updateRestrictions(
+ UserHandle.USER_SYSTEM,
+ dpms.getDeviceOwnerAdminLocked().getLocalUserRestrictions(
+ UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
+ verify(getServices().userManagerInternal)
+ .setDevicePolicyUserRestrictions(eq(UserHandle.USER_SYSTEM),
+ MockUtils.checkUserRestrictions(globalRestrictions),
+ MockUtils.checkUserRestrictions(
+ UserHandle.USER_SYSTEM, localRestrictions),
+ eq(true));
+ reset(getServices().userManagerInternal);
+
+ dpm.clearUserRestriction(admin1, restriction);
+ reset(getServices().userManagerInternal);
+ }
+ }
+ }
+
+ @Test
+ public void testSetLockTaskFeatures_financeDo_validLockTaskFeatures_lockTaskFeaturesSet()
+ throws Exception {
+ int validLockTaskFeatures = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
+ | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+ | LOCK_TASK_FEATURE_NOTIFICATIONS;
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setLockTaskFeatures(admin1, validLockTaskFeatures);
+
+ verify(getServices().iactivityTaskManager)
+ .updateLockTaskFeatures(eq(UserHandle.USER_SYSTEM), eq(validLockTaskFeatures));
+ }
+
+ @Test
+ public void testSetLockTaskFeatures_financeDo_invalidLockTaskFeatures_throwsException()
+ throws Exception {
+ int invalidLockTaskFeatures = LOCK_TASK_FEATURE_NONE | LOCK_TASK_FEATURE_OVERVIEW
+ | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+ // Called during setup.
+ verify(getServices().iactivityTaskManager).updateLockTaskFeatures(anyInt(), anyInt());
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setLockTaskFeatures(admin1, invalidLockTaskFeatures));
+
+ verifyNoMoreInteractions(getServices().iactivityTaskManager);
+ }
+
+ @Test
+ public void testIsUninstallBlocked_financeDo_success() throws Exception {
+ String packageName = "com.android.foo.package";
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+ when(getServices().ipackageManager.getBlockUninstallForUser(
+ eq(packageName), eq(UserHandle.USER_SYSTEM)))
+ .thenReturn(true);
+
+ assertThat(dpm.isUninstallBlocked(admin1, packageName)).isTrue();
+ }
+
+ @Test
+ public void testSetUninstallBlocked_financeDo_success() throws Exception {
+ String packageName = "com.android.foo.package";
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setUninstallBlocked(admin1, packageName, false);
+
+ verify(getServices().ipackageManager)
+ .setBlockUninstallForUser(eq(packageName), eq(false),
+ eq(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testSetUserControlDisabledPackages_financeDo_success() throws Exception {
+ List<String> packages = new ArrayList<>();
+ packages.add("com.android.foo.package");
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setUserControlDisabledPackages(admin1, packages);
+
+ verify(getServices().packageManagerInternal)
+ .setDeviceOwnerProtectedPackages(eq(admin1.getPackageName()), eq(packages));
+ }
+
+ @Test
+ public void testGetUserControlDisabledPackages_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertThat(dpm.getUserControlDisabledPackages(admin1)).isEmpty();
+ }
+
+ @Test
+ public void testSetOrganizationName_financeDo_success() throws Exception {
+ String organizationName = "Test Organization";
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setOrganizationName(admin1, organizationName);
+
+ assertThat(dpm.getDeviceOwnerOrganizationName()).isEqualTo(organizationName);
+ }
+
+ @Test
+ public void testSetShortSupportMessage_financeDo_success() throws Exception {
+ String supportMessage = "Test short support message";
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setShortSupportMessage(admin1, supportMessage);
+
+ assertThat(dpm.getShortSupportMessage(admin1)).isEqualTo(supportMessage);
+ }
+
+ @Test
+ public void testIsBackupServiceEnabled_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+ when(getServices().ibackupManager.isBackupServiceActive(eq(UserHandle.USER_SYSTEM)))
+ .thenReturn(true);
+
+ assertThat(dpm.isBackupServiceEnabled(admin1)).isTrue();
+ }
+
+ @Test
+ public void testSetBackupServiceEnabled_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setBackupServiceEnabled(admin1, true);
+
+ verify(getServices().ibackupManager)
+ .setBackupServiceActive(eq(UserHandle.USER_SYSTEM), eq(true));
+ }
+
+ @Test
+ public void testIsLockTaskPermitted_financeDo_success() throws Exception {
+ String packageName = "com.android.foo.package";
+ mockPolicyExemptApps(packageName);
+ mockVendorPolicyExemptApps();
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertThat(dpm.isLockTaskPermitted(packageName)).isTrue();
+ }
+
+ @Test
+ public void testSetLockTaskPackages_financeDo_success() throws Exception {
+ String[] packages = {"com.android.foo.package"};
+ mockEmptyPolicyExemptApps();
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setLockTaskPackages(admin1, packages);
+
+ verify(getServices().iactivityManager)
+ .updateLockTaskPackages(eq(UserHandle.USER_SYSTEM), eq(packages));
+ }
+
+ @Test
+ public void testAddPersistentPreferredActivity_financeDo_success() throws Exception {
+ IntentFilter filter = new IntentFilter();
+ ComponentName target = new ComponentName(admin2.getPackageName(), "test.class");
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.addPersistentPreferredActivity(admin1, filter, target);
+
+ verify(getServices().ipackageManager)
+ .addPersistentPreferredActivity(eq(filter), eq(target), eq(UserHandle.USER_SYSTEM));
+ verify(getServices().ipackageManager)
+ .flushPackageRestrictionsAsUser(eq(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testClearPackagePersistentPreferredActvities_financeDo_success() throws Exception {
+ String packageName = admin2.getPackageName();
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.clearPackagePersistentPreferredActivities(admin1, packageName);
+
+ verify(getServices().ipackageManager)
+ .clearPackagePersistentPreferredActivities(
+ eq(packageName), eq(UserHandle.USER_SYSTEM));
+ verify(getServices().ipackageManager)
+ .flushPackageRestrictionsAsUser(eq(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testWipeData_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+ when(getServices().userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_FACTORY_RESET,
+ UserHandle.SYSTEM))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+ when(mMockContext.getResources()
+ .getString(R.string.work_profile_deleted_description_dpm_wipe))
+ .thenReturn("Test string");
+
+ dpm.wipeData(0);
+
+ verifyRebootWipeUserData(/* wipeEuicc= */ false);
+ }
+
+ @Test
+ public void testIsDeviceOwnerApp_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertThat(dpm.isDeviceOwnerApp(admin1.getPackageName())).isTrue();
+ }
+
+ @Test
+ public void testClearDeviceOwnerApp_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.clearDeviceOwnerApp(admin1.getPackageName());
+
+ assertThat(dpm.getDeviceOwnerComponentOnAnyUser()).isNull();
+ assertThat(dpm.isAdminActiveAsUser(admin1, UserHandle.USER_SYSTEM)).isFalse();
+ verify(mMockContext.spiedContext, times(2))
+ .sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED),
+ eq(UserHandle.SYSTEM));
+ }
+
+ @Test
+ public void testSetPermissionGrantState_financeDo_notReadPhoneStatePermission_throwsException()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setPermissionGrantState(admin1, admin1.getPackageName(),
+ permission.READ_CALENDAR,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED));
+ }
+
+ @Test
+ public void testSetPermissionGrantState_financeDo_grantPermissionToNonDeviceOwnerPackage_throwsException()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setPermissionGrantState(admin1, "com.android.foo.package",
+ permission.READ_PHONE_STATE,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED));
+ }
+
+ @Test
public void testSetUsbDataSignalingEnabled_noDeviceOwnerOrPoOfOrgOwnedDevice() {
assertThrows(SecurityException.class,
() -> dpm.setUsbDataSignalingEnabled(true));
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index 15f3ed1..4cb46b4 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -109,6 +109,14 @@
public static Bundle checkUserRestrictions(String... keys) {
final Bundle expected = DpmTestUtils.newRestrictions(
java.util.Objects.requireNonNull(keys));
+ return checkUserRestrictions(expected);
+ }
+
+ public static Bundle checkUserRestrictions(Bundle expected) {
+ return createUserRestrictionsBundleMatcher(expected);
+ }
+
+ private static Bundle createUserRestrictionsBundleMatcher(Bundle expected) {
final Matcher<Bundle> m = new BaseMatcher<Bundle>() {
@Override
public boolean matches(Object item) {
@@ -129,6 +137,15 @@
public static RestrictionsSet checkUserRestrictions(int userId, String... keys) {
final RestrictionsSet expected = DpmTestUtils.newRestrictions(userId,
java.util.Objects.requireNonNull(keys));
+ return checkUserRestrictions(userId, expected);
+ }
+
+ public static RestrictionsSet checkUserRestrictions(int userId, RestrictionsSet expected) {
+ return createUserRestrictionsSetMatcher(userId, expected);
+ }
+
+ private static RestrictionsSet createUserRestrictionsSetMatcher(
+ int userId, RestrictionsSet expected) {
final Matcher<RestrictionsSet> m = new BaseMatcher<RestrictionsSet>() {
@Override
public boolean matches(Object item) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index a8f24ce..533fb2d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -26,6 +26,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
@@ -76,6 +77,7 @@
private static final ComponentName TEST_MDM_COMPONENT_NAME = new ComponentName(
TEST_DPC_PACKAGE_NAME, "pc.package.name.DeviceAdmin");
private static final int TEST_USER_ID = 123;
+ private static final String ROLE_HOLDER_PACKAGE_NAME = "test.role.holder.package.name";
private @Mock Resources mResources;
@@ -305,6 +307,26 @@
ACTION_PROVISION_MANAGED_PROFILE, "package1", "package2", "package3");
}
+ @Test
+ public void testGetNonRequiredApps_managedProfile_roleHolder_works() {
+ when(mInjector.getDeviceManagerRoleHolderPackageName(any()))
+ .thenReturn(ROLE_HOLDER_PACKAGE_NAME);
+ setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME);
+
+ verifyAppsAreNonRequired(
+ ACTION_PROVISION_MANAGED_PROFILE, "package1", "package2");
+ }
+
+ @Test
+ public void testGetNonRequiredApps_managedDevice_roleHolder_works() {
+ when(mInjector.getDeviceManagerRoleHolderPackageName(any()))
+ .thenReturn(ROLE_HOLDER_PACKAGE_NAME);
+ setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME);
+
+ verifyAppsAreNonRequired(
+ ACTION_PROVISION_MANAGED_DEVICE, "package1", "package2");
+ }
+
private void setupRegularModulesWithManagedUser(String... regularModules) {
setupRegularModulesWithMetadata(regularModules, REQUIRED_APP_MANAGED_USER);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 02a8ae8..9a5254d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -93,7 +93,7 @@
assertThat(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)).isFalse();
owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(),
- DEVICE_OWNER_TYPE_FINANCED);
+ DEVICE_OWNER_TYPE_FINANCED, /* isAdminTestOnly= */ false);
// There is no device owner, so the default owner type should be returned.
assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo(
DEVICE_OWNER_TYPE_DEFAULT);
@@ -367,7 +367,7 @@
owners.setDeviceOwnerUserRestrictionsMigrated();
owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(),
- DEVICE_OWNER_TYPE_FINANCED);
+ DEVICE_OWNER_TYPE_FINANCED, /* isAdminTestOnly= */ false);
assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo(
DEVICE_OWNER_TYPE_FINANCED);
@@ -399,7 +399,7 @@
owners.setProfileOwnerUserRestrictionsMigrated(11);
owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(),
- DEVICE_OWNER_TYPE_DEFAULT);
+ DEVICE_OWNER_TYPE_DEFAULT, /* isAdminTestOnly= */ false);
// The previous device owner type should persist.
assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo(
DEVICE_OWNER_TYPE_FINANCED);
@@ -585,7 +585,8 @@
assertThat(owners.getProfileOwnerFile(11).exists()).isTrue();
String previousDeviceOwnerPackageName = owners.getDeviceOwnerPackageName();
- owners.setDeviceOwnerType(previousDeviceOwnerPackageName, DEVICE_OWNER_TYPE_FINANCED);
+ owners.setDeviceOwnerType(previousDeviceOwnerPackageName, DEVICE_OWNER_TYPE_FINANCED,
+ /* isAdminTestOnly= */ false);
assertThat(owners.getDeviceOwnerType(previousDeviceOwnerPackageName)).isEqualTo(
DEVICE_OWNER_TYPE_FINANCED);
owners.setDeviceOwnerProtectedPackages(
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index d2cff0e..fe3034d 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -326,7 +326,7 @@
assertEquals(callback.getLastNotifiedInfo().currentState,
OTHER_DEVICE_STATE.getIdentifier());
- mService.getBinderService().cancelRequest(token);
+ mService.getBinderService().cancelStateRequest();
flushHandler();
assertEquals(callback.getLastNotifiedStatus(token),
@@ -378,9 +378,9 @@
mPolicy.resumeConfigureOnce();
flushHandler();
- // First request status is now suspended as there is another pending request.
+ // First request status is now canceled as there is another pending request.
assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
- TestDeviceStateManagerCallback.STATUS_SUSPENDED);
+ TestDeviceStateManagerCallback.STATUS_CANCELED);
// Second request status still unknown because the service is still awaiting policy
// callback.
assertEquals(callback.getLastNotifiedStatus(secondRequestToken),
@@ -402,19 +402,19 @@
DEFAULT_DEVICE_STATE.getIdentifier());
// Now cancel the second request to make the first request active.
- mService.getBinderService().cancelRequest(secondRequestToken);
+ mService.getBinderService().cancelStateRequest();
flushHandler();
assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
- TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ TestDeviceStateManagerCallback.STATUS_CANCELED);
assertEquals(callback.getLastNotifiedStatus(secondRequestToken),
TestDeviceStateManagerCallback.STATUS_CANCELED);
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
+ assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ DEFAULT_DEVICE_STATE.getIdentifier());
}
@Test
@@ -656,11 +656,6 @@
}
@Override
- public void onRequestSuspended(IBinder token) {
- mLastNotifiedStatus.put(token, STATUS_SUSPENDED);
- }
-
- @Override
public void onRequestCanceled(IBinder token) {
mLastNotifiedStatus.put(token, STATUS_CANCELED);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index b94fc43..2297c91 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -18,7 +18,6 @@
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
-import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
@@ -66,7 +65,7 @@
}
@Test
- public void addRequest_suspendExistingRequest() {
+ public void addRequest_cancelExistingRequestThroughNewRequest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
assertNull(mStatusListener.getLastStatus(firstRequest));
@@ -75,92 +74,52 @@
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
+ 1 /* requestedState */, 0 /* flags */);
assertNull(mStatusListener.getLastStatus(secondRequest));
mController.addRequest(secondRequest);
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@Test
public void addRequest_cancelActiveRequest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
- mController.cancelRequest(secondRequest.getToken());
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
- }
- @Test
- public void addRequest_cancelSuspendedRequest() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
+ mController.cancelOverrideRequest();
- mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
- mController.cancelRequest(firstRequest.getToken());
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@Test
public void handleBaseStateChanged() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */,
DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */);
mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
mController.handleBaseStateChanged();
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@Test
public void handleProcessDied() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
- mController.handleProcessDied(1);
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
mController.handleProcessDied(0);
-
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@@ -170,46 +129,29 @@
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
- mController.handleProcessDied(1);
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
- mController.cancelStickyRequests();
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+ mController.handleProcessDied(0);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+ mController.cancelStickyRequest();
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@Test
public void handleNewSupportedStates() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
1 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 2 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
mController.handleNewSupportedStates(new int[]{ 0, 1 });
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
mController.handleNewSupportedStates(new int[]{ 0 });
-
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@@ -217,18 +159,11 @@
public void cancelOverrideRequestsTest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
1 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 2 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
- mController.cancelOverrideRequests();
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
+ mController.cancelOverrideRequest();
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 4caa85c..f5a5689 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -81,6 +81,7 @@
@Mock HysteresisLevels mScreenBrightnessThresholds;
@Mock Handler mNoOpHandler;
@Mock HighBrightnessModeController mHbmController;
+ @Mock BrightnessThrottler mBrightnessThrottler;
@Before
public void setUp() {
@@ -128,12 +129,15 @@
INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
- mContext, mHbmController, mIdleBrightnessMappingStrategy,
+ mContext, mHbmController, mBrightnessThrottler, mIdleBrightnessMappingStrategy,
AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG
);
when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT);
when(mHbmController.getCurrentBrightnessMin()).thenReturn(BRIGHTNESS_MIN_FLOAT);
+ // Disable brightness throttling by default. Individual tests can enable it as needed.
+ when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+ when(mBrightnessThrottler.isThrottled()).thenReturn(false);
// Configure the brightness controller and grab an instance of the sensor listener,
// through which we can deliver fake (for test) sensor values.
@@ -420,4 +424,47 @@
assertEquals(600f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
assertEquals(250f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
}
+
+ @Test
+ public void testBrightnessGetsThrottled() throws Exception {
+ Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
+ mController = setupController(lightSensor);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Set up system to return max brightness at 100 lux
+ final float normalizedBrightness = BRIGHTNESS_MAX_FLOAT;
+ final float lux = 100.0f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux))
+ .thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux))
+ .thenReturn(lux);
+ when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt()))
+ .thenReturn(normalizedBrightness);
+
+ // Sensor reads 100 lux. We should get max brightness.
+ listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux));
+ assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
+
+ // Apply throttling and notify ABC (simulates DisplayPowerController#updatePowerState())
+ final float throttledBrightness = 0.123f;
+ when(mBrightnessThrottler.getBrightnessCap()).thenReturn(throttledBrightness);
+ when(mBrightnessThrottler.isThrottled()).thenReturn(true);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
+ BRIGHTNESS_MAX_FLOAT /* brightness */, false /* userChangedBrightness */,
+ 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ assertEquals(throttledBrightness, mController.getAutomaticScreenBrightness(), 0.0f);
+
+ // Remove throttling and notify ABC again
+ when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+ when(mBrightnessThrottler.isThrottled()).thenReturn(false);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
+ BRIGHTNESS_MAX_FLOAT /* brightness */, false /* userChangedBrightness */,
+ 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index b7af010..6203c2f 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -663,6 +663,30 @@
eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING));
}
+ @Test
+ public void tetHbmStats_LowRequestedBrightness() {
+ final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+ final int displayStatsId = mDisplayUniqueId.hashCode();
+
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+ hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+ advanceTime(0);
+ // verify in HBM sunlight mode
+ assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+ // verify HBM_ON_SUNLIGHT
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+ hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
+ // verify HBM_SV_OFF due to LOW_REQUESTED_BRIGHTNESS
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+ eq(FrameworkStatsLog
+ .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS));
+ }
+
private void assertState(HighBrightnessModeController hbmc,
float brightnessMin, float brightnessMax, int hbmMode) {
assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON);
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
index 0f3742f..ac97911 100644
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -59,6 +59,8 @@
@RunWith(JUnit4.class)
public final class AmbientLuxTest {
+
+ private static final float ALLOWED_ERROR_DELTA = 0.001f;
private static final int AMBIENT_COLOR_TYPE = 20705;
private static final String AMBIENT_COLOR_TYPE_STR = "colorSensoryDensoryDoc";
private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE = 5432.1f;
@@ -78,6 +80,8 @@
@Mock private TypedArray mHighLightBiases;
@Mock private TypedArray mAmbientColorTemperatures;
@Mock private TypedArray mDisplayColorTemperatures;
+ @Mock private TypedArray mStrongAmbientColorTemperatures;
+ @Mock private TypedArray mStrongDisplayColorTemperatures;
@Mock private ColorDisplayService.ColorDisplayServiceInternal mColorDisplayServiceInternalMock;
@Before
@@ -110,6 +114,12 @@
when(mResourcesSpy.obtainTypedArray(
R.array.config_displayWhiteBalanceDisplayColorTemperatures))
.thenReturn(mDisplayColorTemperatures);
+ when(mResourcesSpy.obtainTypedArray(
+ R.array.config_displayWhiteBalanceStrongAmbientColorTemperatures))
+ .thenReturn(mStrongAmbientColorTemperatures);
+ when(mResourcesSpy.obtainTypedArray(
+ R.array.config_displayWhiteBalanceStrongDisplayColorTemperatures))
+ .thenReturn(mStrongDisplayColorTemperatures);
when(mResourcesSpy.obtainTypedArray(
R.array.config_displayWhiteBalanceLowLightAmbientBrightnesses))
@@ -375,6 +385,43 @@
}
@Test
+ public void testStrongMode() {
+ final float lowerBrightness = 10.0f;
+ final float upperBrightness = 50.0f;
+ setBrightnesses(lowerBrightness, upperBrightness);
+ setBiases(0.0f, 1.0f);
+ final int ambientColorTempLow = 6000;
+ final int ambientColorTempHigh = 8000;
+ final int displayColorTempLow = 6400;
+ final int displayColorTempHigh = 7400;
+ setStrongAmbientColorTemperatures(ambientColorTempLow, ambientColorTempHigh);
+ setStrongDisplayColorTemperatures(displayColorTempLow, displayColorTempHigh);
+
+ DisplayWhiteBalanceController controller =
+ DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+ controller.setStrongModeEnabled(true);
+ controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+ for (float ambientTempFraction = 0.0f; ambientTempFraction <= 1.0f;
+ ambientTempFraction += 0.1f) {
+ final float ambientTemp =
+ (ambientColorTempHigh - ambientColorTempLow) * ambientTempFraction
+ + ambientColorTempLow;
+ setEstimatedColorTemperature(controller, ambientTemp);
+ for (float brightnessFraction = 0.0f; brightnessFraction <= 1.0f;
+ brightnessFraction += 0.1f) {
+ setEstimatedBrightnessAndUpdate(controller,
+ mix(lowerBrightness, upperBrightness, brightnessFraction));
+ assertEquals(controller.mPendingAmbientColorTemperature,
+ mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE,
+ mix(displayColorTempLow, displayColorTempHigh, ambientTempFraction),
+ brightnessFraction),
+ ALLOWED_ERROR_DELTA);
+ }
+ }
+ }
+
+ @Test
public void testLowLight_DefaultAmbient() throws Exception {
final float lowerBrightness = 10.0f;
final float upperBrightness = 50.0f;
@@ -486,6 +533,14 @@
setFloatArrayResource(mDisplayColorTemperatures, vals);
}
+ private void setStrongAmbientColorTemperatures(float... vals) {
+ setFloatArrayResource(mStrongAmbientColorTemperatures, vals);
+ }
+
+ private void setStrongDisplayColorTemperatures(float... vals) {
+ setFloatArrayResource(mStrongDisplayColorTemperatures, vals);
+ }
+
private void setFloatArrayResource(TypedArray array, float[] vals) {
when(array.length()).thenReturn(vals.length);
for (int i = 0; i < vals.length; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
index d441143..0028969 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
@@ -165,6 +165,17 @@
R.bool.config_cecTvSendStandbyOnSleepDisabled_default);
doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecSetMenuLanguage_userConfigurable);
+ doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecSetMenuLanguageEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecSetMenuLanguageEnabled_default);
+ doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecSetMenuLanguageDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(
+ R.bool.config_cecSetMenuLanguageDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(
R.bool.config_cecRcProfileTv_userConfigurable);
doReturn(true).when(resources).getBoolean(
R.bool.config_cecRcProfileTvNone_allowed);
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 85d30a6..8e756ae 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
@@ -83,6 +83,7 @@
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
@@ -121,6 +122,7 @@
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
@@ -159,6 +161,7 @@
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
@@ -317,8 +320,10 @@
@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/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 7751ef5..c48a974 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -42,6 +42,7 @@
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
import android.hardware.hdmi.IHdmiControlStatusChangeListener;
+import android.hardware.hdmi.IHdmiVendorCommandListener;
import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
@@ -747,6 +748,114 @@
}
@Test
+ public void addVendorCommandListener_receiveCallback_VendorCmdNoIdTest() {
+ int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress();
+ int sourceAddress = Constants.ADDR_TV;
+ byte[] params = {0x00, 0x01, 0x02, 0x03};
+ int vendorId = 0x123456;
+
+ VendorCommandListener vendorCmdListener =
+ new VendorCommandListener(sourceAddress, destAddress, params, vendorId);
+ mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage vendorCommandNoId =
+ HdmiCecMessageBuilder.buildVendorCommand(sourceAddress, destAddress, params);
+ mNativeWrapper.onCecMessage(vendorCommandNoId);
+ mTestLooper.dispatchAll();
+ assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isTrue();
+ assertThat(vendorCmdListener.mParamsCorrect).isTrue();
+ assertThat(vendorCmdListener.mHasVendorId).isFalse();
+ }
+
+ @Test
+ public void addVendorCommandListener_receiveCallback_VendorCmdWithIdTest() {
+ int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress();
+ int sourceAddress = Constants.ADDR_TV;
+ byte[] params = {0x00, 0x01, 0x02, 0x03};
+ int vendorId = 0x123456;
+
+ VendorCommandListener vendorCmdListener =
+ new VendorCommandListener(sourceAddress, destAddress, params, vendorId);
+ mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage vendorCommandWithId =
+ HdmiCecMessageBuilder.buildVendorCommandWithId(
+ sourceAddress, destAddress, vendorId, params);
+ mNativeWrapper.onCecMessage(vendorCommandWithId);
+ mTestLooper.dispatchAll();
+ assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isTrue();
+ assertThat(vendorCmdListener.mParamsCorrect).isTrue();
+ assertThat(vendorCmdListener.mHasVendorId).isTrue();
+ }
+
+ @Test
+ public void addVendorCommandListener_noCallback_VendorCmdDiffIdTest() {
+ int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress();
+ int sourceAddress = Constants.ADDR_TV;
+ byte[] params = {0x00, 0x01, 0x02, 0x03};
+ int vendorId = 0x123456;
+ int diffVendorId = 0x345678;
+
+ VendorCommandListener vendorCmdListener =
+ new VendorCommandListener(sourceAddress, destAddress, params, vendorId);
+ mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage vendorCommandWithDiffId =
+ HdmiCecMessageBuilder.buildVendorCommandWithId(
+ sourceAddress, destAddress, diffVendorId, params);
+ mNativeWrapper.onCecMessage(vendorCommandWithDiffId);
+ mTestLooper.dispatchAll();
+ assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isFalse();
+ }
+
+ private static class VendorCommandListener extends IHdmiVendorCommandListener.Stub {
+ boolean mVendorCommandCallbackReceived = false;
+ boolean mParamsCorrect = false;
+ boolean mHasVendorId = false;
+
+ int mSourceAddress;
+ int mDestAddress;
+ byte[] mParams;
+ int mVendorId;
+
+ VendorCommandListener(int sourceAddress, int destAddress, byte[] params, int vendorId) {
+ this.mSourceAddress = sourceAddress;
+ this.mDestAddress = destAddress;
+ this.mParams = params.clone();
+ this.mVendorId = vendorId;
+ }
+
+ @Override
+ public void onReceived(
+ int sourceAddress, int destAddress, byte[] params, boolean hasVendorId) {
+ mVendorCommandCallbackReceived = true;
+ if (mSourceAddress == sourceAddress && mDestAddress == destAddress) {
+ byte[] expectedParams;
+ if (hasVendorId) {
+ // If the command has vendor ID, we have to add it to mParams.
+ expectedParams = new byte[params.length];
+ expectedParams[0] = (byte) ((mVendorId >> 16) & 0xFF);
+ expectedParams[1] = (byte) ((mVendorId >> 8) & 0xFF);
+ expectedParams[2] = (byte) (mVendorId & 0xFF);
+ System.arraycopy(mParams, 0, expectedParams, 3, mParams.length);
+ } else {
+ expectedParams = params.clone();
+ }
+ if (Arrays.equals(expectedParams, params)) {
+ mParamsCorrect = true;
+ }
+ }
+ mHasVendorId = hasVendorId;
+ }
+
+ @Override
+ public void onControlStateChanged(boolean enabled, int reason) {}
+ }
+
+ @Test
public void dispatchMessageToLocalDevice_broadcastMessage_returnsHandled() {
HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby(
Constants.ADDR_TV,
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index 7f7c716..2f5993d1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -61,7 +61,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.Map;
+import java.util.List;
@SmallTest
@Presubmit
@@ -136,9 +136,10 @@
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
- Map<String, String> services = mApexManager.getApexSystemServices();
+ List<ApexSystemServiceInfo> services = mApexManager.getApexSystemServices();
assertThat(services).hasSize(1);
- assertThat(services).containsKey("com.android.apex.test.ApexSystemService");
+ assertThat(services.stream().map(ApexSystemServiceInfo::getName).findFirst().orElse(null))
+ .matches("com.android.apex.test.ApexSystemService");
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
index b621a44..869ac88 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
@@ -70,7 +70,7 @@
import androidx.test.filters.Suppress;
import com.android.frameworks.servicestests.R;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.server.pm.pkg.parsing.ParsingPackage;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
@@ -106,11 +106,11 @@
private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec";
- private static final int APP_INSTALL_AUTO = PackageHelper.APP_INSTALL_AUTO;
+ private static final int APP_INSTALL_AUTO = InstallLocationUtils.APP_INSTALL_AUTO;
- private static final int APP_INSTALL_DEVICE = PackageHelper.APP_INSTALL_INTERNAL;
+ private static final int APP_INSTALL_DEVICE = InstallLocationUtils.APP_INSTALL_INTERNAL;
- private static final int APP_INSTALL_SDCARD = PackageHelper.APP_INSTALL_EXTERNAL;
+ private static final int APP_INSTALL_SDCARD = InstallLocationUtils.APP_INSTALL_EXTERNAL;
void failStr(String errMsg) {
Log.w(TAG, "errMsg=" + errMsg);
@@ -1214,7 +1214,7 @@
int origDefaultLoc = getDefaultInstallLoc();
InstallParams ip = null;
try {
- setInstallLoc(PackageHelper.APP_INSTALL_AUTO);
+ setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO);
// Install first
ip = installFromRawResource("install.apk", rawResId, installFlags, false,
false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
@@ -1303,7 +1303,7 @@
InstallParams ip = null;
try {
PackageManager pm = getPm();
- setInstallLoc(PackageHelper.APP_INSTALL_AUTO);
+ setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO);
// Install first
ip = installFromRawResource("install.apk", R.raw.install, installFlags, false,
false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
@@ -1517,11 +1517,11 @@
int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
boolean enable = getUserSettingSetInstallLocation();
if (enable) {
- if (userSetting == PackageHelper.APP_INSTALL_AUTO) {
+ if (userSetting == InstallLocationUtils.APP_INSTALL_AUTO) {
iloc = PackageInfo.INSTALL_LOCATION_AUTO;
- } else if (userSetting == PackageHelper.APP_INSTALL_EXTERNAL) {
+ } else if (userSetting == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
iloc = PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL;
- } else if (userSetting == PackageHelper.APP_INSTALL_INTERNAL) {
+ } else if (userSetting == InstallLocationUtils.APP_INSTALL_INTERNAL) {
iloc = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
}
}
@@ -1552,7 +1552,7 @@
}
@LargeTest
public void testExistingIUserI() throws Exception {
- int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+ int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
int iFlags = PackageManager.INSTALL_INTERNAL;
setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
}
@@ -1564,14 +1564,14 @@
return;
}
- int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+ int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
int iFlags = PackageManager.INSTALL_INTERNAL;
setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
}
@LargeTest
public void testExistingIUserA() throws Exception {
- int userSetting = PackageHelper.APP_INSTALL_AUTO;
+ int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
int iFlags = PackageManager.INSTALL_INTERNAL;
setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
}
@@ -1616,7 +1616,7 @@
}
@LargeTest
public void testUserI() throws Exception {
- int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+ int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
int iloc = getExpectedInstallLocation(userSetting);
setUserX(true, userSetting, iloc);
}
@@ -1628,14 +1628,14 @@
return;
}
- int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+ int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
int iloc = getExpectedInstallLocation(userSetting);
setUserX(true, userSetting, iloc);
}
@LargeTest
public void testUserA() throws Exception {
- int userSetting = PackageHelper.APP_INSTALL_AUTO;
+ int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
int iloc = getExpectedInstallLocation(userSetting);
setUserX(true, userSetting, iloc);
}
@@ -1646,7 +1646,7 @@
*/
@LargeTest
public void testUserPrefOffUserI() throws Exception {
- int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+ int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
setUserX(false, userSetting, iloc);
}
@@ -1658,14 +1658,14 @@
return;
}
- int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+ int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
setUserX(false, userSetting, iloc);
}
@LargeTest
public void testUserPrefOffA() throws Exception {
- int userSetting = PackageHelper.APP_INSTALL_AUTO;
+ int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
setUserX(false, userSetting, iloc);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 4a24bbd..8e53ca1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -17,7 +17,7 @@
package com.android.server.pm;
import static android.content.pm.SharedLibraryInfo.TYPE_DYNAMIC;
-import static android.content.pm.SharedLibraryInfo.TYPE_SDK;
+import static android.content.pm.SharedLibraryInfo.TYPE_SDK_PACKAGE;
import static android.content.pm.SharedLibraryInfo.TYPE_STATIC;
import static android.content.pm.SharedLibraryInfo.VERSION_UNDEFINED;
@@ -258,7 +258,7 @@
assertThat(scanResult.mSdkSharedLibraryInfo.getPackageName(), is("ogl.sdk_123"));
assertThat(scanResult.mSdkSharedLibraryInfo.getName(), is("ogl.sdk"));
assertThat(scanResult.mSdkSharedLibraryInfo.getLongVersion(), is(123L));
- assertThat(scanResult.mSdkSharedLibraryInfo.getType(), is(TYPE_SDK));
+ assertThat(scanResult.mSdkSharedLibraryInfo.getType(), is(TYPE_SDK_PACKAGE));
assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getPackageName(),
is("ogl.sdk_123"));
assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getLongVersionCode(),
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 51dbd97..827349a 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -64,6 +64,7 @@
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IWakeLockCallback;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerSaveState;
@@ -102,6 +103,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
@@ -479,21 +481,21 @@
// First, ensure that a normal full wake lock does not cause a wakeup
int flags = PowerManager.FULL_WAKE_LOCK;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
// Ensure that the flag does *NOT* work with a partial wake lock.
flags = PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
// Verify that flag forces a wakeup when paired to a FULL_WAKE_LOCK
flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
}
@@ -661,12 +663,12 @@
wakelockMap.put((String) inv.getArguments()[1], (int) inv.getArguments()[0]);
return null;
}).when(mNotifierMock).onWakeLockAcquired(anyInt(), anyString(), anyString(), anyInt(),
- anyInt(), any(), any());
+ anyInt(), any(), any(), any());
doAnswer(inv -> {
wakelockMap.remove((String) inv.getArguments()[1]);
return null;
}).when(mNotifierMock).onWakeLockReleased(anyInt(), anyString(), anyString(), anyInt(),
- anyInt(), any(), any());
+ anyInt(), any(), any(), any());
//
// TEST STARTS HERE
@@ -679,7 +681,7 @@
// Create a wakelock
mService.getBinderServiceInstance().acquireWakeLock(new Binder(), flags, tag, pkg,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(wakelockMap.get(tag)).isEqualTo(flags); // Verify wakelock is active.
// Confirm that the wakelocks have been disabled when the forceSuspend is in flight.
@@ -737,7 +739,7 @@
// Take a nap and verify we no longer hold the blocker
int flags = PowerManager.DOZE_WAKE_LOCK;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
mService.getBinderServiceInstance().goToSleep(mClock.now(),
PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
@@ -893,7 +895,7 @@
mService.getBinderServiceInstance().acquireWakeLock(token,
PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
advanceTime(60);
@@ -919,7 +921,7 @@
mService.getBinderServiceInstance().acquireWakeLock(token,
PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, tag, pkg,
- null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
advanceTime(1500);
mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
@@ -995,7 +997,7 @@
mService.getBinderServiceInstance().acquireWakeLock(token,
PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
- null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
@@ -1035,7 +1037,7 @@
mService.getBinderServiceInstance().acquireWakeLock(token,
PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
@@ -1076,7 +1078,7 @@
mService.getBinderServiceInstance().acquireWakeLock(token,
PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
- null /* workSource */, null /* historyTag */, nonDefaultDisplay);
+ null /* workSource */, null /* historyTag */, nonDefaultDisplay, null);
assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
WAKEFULNESS_AWAKE);
@@ -1640,7 +1642,65 @@
IBinder token = new Binder();
String packageName = "pkg.name";
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+ null /* callback */);
return mService.findWakeLockLocked(token);
}
+
+ /**
+ * Test IPowerManager.acquireWakeLock() with a IWakeLockCallback.
+ */
+ @Test
+ public void testNotifyWakeLockCallback() {
+ createService();
+ startSystem();
+ final String tag = "wakelock1";
+ final String packageName = "pkg.name";
+ final IBinder token = new Binder();
+ final int flags = PowerManager.PARTIAL_WAKE_LOCK;
+ final IWakeLockCallback callback = Mockito.mock(IWakeLockCallback.class);
+ final IBinder callbackBinder = Mockito.mock(Binder.class);
+ when(callback.asBinder()).thenReturn(callbackBinder);
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback);
+ verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
+ anyInt(), any(), any(), same(callback));
+
+ mService.getBinderServiceInstance().releaseWakeLock(token, 0);
+ verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
+ anyInt(), any(), any(), same(callback));
+ }
+
+ /**
+ * Test IPowerManager.updateWakeLockCallback() with a new IWakeLockCallback.
+ */
+ @Test
+ public void testNotifyWakeLockCallbackChange() {
+ createService();
+ startSystem();
+ final String tag = "wakelock1";
+ final String packageName = "pkg.name";
+ final IBinder token = new Binder();
+ int flags = PowerManager.PARTIAL_WAKE_LOCK;
+ final IWakeLockCallback callback1 = Mockito.mock(IWakeLockCallback.class);
+ final IBinder callbackBinder1 = Mockito.mock(Binder.class);
+ when(callback1.asBinder()).thenReturn(callbackBinder1);
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback1);
+ verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
+ anyInt(), any(), any(), same(callback1));
+
+ final IWakeLockCallback callback2 = Mockito.mock(IWakeLockCallback.class);
+ final IBinder callbackBinder2 = Mockito.mock(Binder.class);
+ when(callback2.asBinder()).thenReturn(callbackBinder2);
+ mService.getBinderServiceInstance().updateWakeLockCallback(token, callback2);
+ verify(mNotifierMock).onWakeLockChanging(anyInt(), eq(tag), eq(packageName),
+ anyInt(), anyInt(), any(), any(), same(callback1),
+ anyInt(), eq(tag), eq(packageName), anyInt(), anyInt(), any(), any(),
+ same(callback2));
+
+ mService.getBinderServiceInstance().releaseWakeLock(token, 0);
+ verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
+ anyInt(), any(), any(), same(callback2));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 5eed30b..91d4f8f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -67,6 +67,7 @@
@Mock Vibrator mVibrator;
private final String callPkg = "com.android.server.notification";
+ private final String sysPkg = "android";
private final int callUid = 10;
private String smsPkg;
private final int smsUid = 11;
@@ -79,6 +80,7 @@
private NotificationRecord mRecordHighCall;
private NotificationRecord mRecordHighCallStyle;
private NotificationRecord mRecordEmail;
+ private NotificationRecord mRecordSystemMax;
private NotificationRecord mRecordInlineReply;
private NotificationRecord mRecordSms;
private NotificationRecord mRecordStarredContact;
@@ -191,6 +193,12 @@
mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT);
mRecordContact.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
+ Notification nSystemMax = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
+ mRecordSystemMax = new NotificationRecord(mContext, new StatusBarNotification(sysPkg,
+ sysPkg, 1, "systemmax", uid2, uid2, nSystemMax, new UserHandle(userId),
+ "", 1244), getDefaultChannel());
+ mRecordSystemMax.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
+
Notification n8 = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
pkg2, 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
@@ -267,6 +275,7 @@
}
expected.add(mRecordStarredContact);
expected.add(mRecordContact);
+ expected.add(mRecordSystemMax);
expected.add(mRecordEmail);
expected.add(mRecordUrgent);
expected.add(mNoMediaSessionMedia);
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 7542033..7e27e54 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.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 @@
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/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index fa294dd..3b67182 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -20,8 +20,8 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
-import static android.permission.PermissionManager.PERMISSION_GRANTED;
-import static android.permission.PermissionManager.PERMISSION_SOFT_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.google.common.truth.Truth.assertThat;
@@ -130,13 +130,13 @@
@Test
public void testHasPermission() throws Exception {
- when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS)))
+ when(mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(anyInt()))
.thenReturn(PERMISSION_GRANTED);
assertThat(mPermissionHelper.hasPermission(1)).isTrue();
- when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS)))
- .thenReturn(PERMISSION_SOFT_DENIED);
+ when(mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(anyInt()))
+ .thenReturn(PERMISSION_DENIED);
assertThat(mPermissionHelper.hasPermission(1)).isFalse();
}
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 0bf105d..0552a83 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.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.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 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 @@
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 fb15088..abcc8c1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -17,7 +17,6 @@
package com.android.server.notification;
import static android.app.Notification.CATEGORY_CALL;
-import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
@@ -25,6 +24,7 @@
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
@@ -43,16 +43,20 @@
import android.app.NotificationChannel;
import android.app.NotificationManager.Policy;
import android.media.AudioAttributes;
+import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
+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;
@@ -68,10 +72,24 @@
private NotificationMessagingUtil mMessagingUtil;
private ZenModeFiltering mZenModeFiltering;
+ @Mock private TelephonyManager mTelephonyManager;
+
+ private long mTestStartTime;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mZenModeFiltering = new ZenModeFiltering(mContext, mMessagingUtil);
+
+ // 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() {
@@ -95,6 +113,27 @@
return r;
}
+ private Bundle makeExtrasBundleWithPeople(String[] people) {
+ Bundle extras = new Bundle();
+ extras.putObject(Notification.EXTRA_PEOPLE_LIST, people);
+ return extras;
+ }
+
+ // 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);
+ Notification notification = mock(Notification.class);
+ notification.extras = makeExtrasBundleWithPeople(people);
+ when(sbn.getNotification()).thenReturn(notification);
+ when(r.getSbn()).thenReturn(sbn);
+ when(r.getPhoneNumbers()).thenReturn(numbers);
+ return r;
+ }
+
@Test
public void testIsMessage() {
NotificationRecord r = getNotificationRecord();
@@ -309,4 +348,150 @@
assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
}
+
+ @Test
+ public void testMatchesCallFilter_repeatCallers_directMatch() {
+ // 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(getRecordWithPeopleInfo(mailSource, null));
+
+ // set up policy to only allow repeat callers
+ Policy policy = new Policy(
+ PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+ // check whether matchesCallFilter returns the right thing
+ Bundle inputMatches = makeExtrasBundleWithPeople(new String[]{"mailto:hello.world"});
+ Bundle inputWrong = makeExtrasBundleWithPeople(new String[]{"mailto:nope"});
+ assertTrue(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ inputMatches, null, 0, 0));
+ assertFalse(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ inputWrong, null, 0, 0));
+ }
+
+ @Test
+ public void testMatchesCallFilter_repeatCallers_telephoneVariants() {
+ // set up telephony manager behavior
+ when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
+
+ String[] telSource = new String[]{"tel:+1-617-555-1212"};
+ mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null));
+
+ // set up policy to only allow repeat callers
+ Policy policy = new Policy(
+ PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+ // cases to test:
+ // - identical number
+ // - same number, different formatting
+ // - different number
+ // - garbage
+ Bundle identical = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"});
+ Bundle same = makeExtrasBundleWithPeople(new String[]{"tel:16175551212"});
+ Bundle different = makeExtrasBundleWithPeople(new String[]{"tel:123-456-7890"});
+ Bundle garbage = makeExtrasBundleWithPeople(new String[]{"asdfghjkl;"});
+
+ assertTrue("identical numbers should match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ identical, null, 0, 0));
+ assertTrue("equivalent but non-identical numbers should match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ same, null, 0, 0));
+ assertFalse("non-equivalent numbers should not match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ different, null, 0, 0));
+ assertFalse("non-tel strings should not match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ garbage, null, 0, 0));
+ }
+
+ @Test
+ public void testMatchesCallFilter_repeatCallers_urlEncodedTels() {
+ // this is not intended to be a supported case but is one that we have seen
+ // sometimes in the wild, so make sure we handle url-encoded telephone numbers correctly
+ // when somebody provides one.
+
+ // set up telephony manager behavior
+ when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
+
+ String[] telSource = new String[]{"tel:%2B16175551212"};
+ mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null));
+
+ // set up policy to only allow repeat callers
+ Policy policy = new Policy(
+ PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+ // test cases for various forms of the same phone number and different ones
+ Bundle same1 = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"});
+ Bundle same2 = makeExtrasBundleWithPeople(new String[]{"tel:%2B1-617-555-1212"});
+ Bundle same3 = makeExtrasBundleWithPeople(new String[]{"tel:6175551212"});
+ Bundle different1 = makeExtrasBundleWithPeople(new String[]{"tel:%2B16175553434"});
+ Bundle different2 = makeExtrasBundleWithPeople(new String[]{"tel:+16175553434"});
+
+ assertTrue("same number 1 should match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ same1, null, 0, 0));
+ assertTrue("same number 2 should match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ same2, null, 0, 0));
+ assertTrue("same number 3 should match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ same3, null, 0, 0));
+ assertFalse("different number 1 should not match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ different1, null, 0, 0));
+ 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/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 30ad1f9..3298d11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -85,7 +85,6 @@
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
@@ -3114,7 +3113,7 @@
}
@Test
- public void testInClosingAnimation_doNotHideSurface() {
+ public void testInClosingAnimation_visibilityNotCommitted_doNotHideSurface() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
makeWindowVisibleAndDrawn(app);
@@ -3123,16 +3122,45 @@
mDisplayContent.mClosingApps.add(app.mActivityRecord);
mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
- // Update visibility and call to remove window
- app.mActivityRecord.commitVisibility(false, false);
+ // Remove window during transition, so it is requested to hide, but won't be committed until
+ // the transition is finished.
+ app.mActivityRecord.onRemovedFromDisplay();
+
+ assertTrue(mDisplayContent.mClosingApps.contains(app.mActivityRecord));
+ assertFalse(app.mActivityRecord.isVisibleRequested());
+ assertTrue(app.mActivityRecord.isVisible());
+ assertTrue(app.mActivityRecord.isSurfaceShowing());
+
+ // Start transition.
app.mActivityRecord.prepareSurfaces();
// Because the app is waiting for transition, it should not hide the surface.
assertTrue(app.mActivityRecord.isSurfaceShowing());
+ }
- // Ensure onAnimationFinished will callback when the closing animation is finished.
- verify(app.mActivityRecord).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
- eq(null));
+ @Test
+ public void testInClosingAnimation_visibilityCommitted_hideSurface() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ makeWindowVisibleAndDrawn(app);
+
+ // Put the activity in close transition.
+ mDisplayContent.mOpeningApps.clear();
+ mDisplayContent.mClosingApps.add(app.mActivityRecord);
+ mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
+
+ // Commit visibility before start transition.
+ app.mActivityRecord.commitVisibility(false, false);
+
+ assertFalse(app.mActivityRecord.isVisibleRequested());
+ assertFalse(app.mActivityRecord.isVisible());
+ assertTrue(app.mActivityRecord.isSurfaceShowing());
+
+ // Start transition.
+ app.mActivityRecord.prepareSurfaces();
+
+ // Because the app visibility has been committed before the transition start, it should hide
+ // the surface.
+ assertFalse(app.mActivityRecord.isSurfaceShowing());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 7c340ec..8ada971 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -191,7 +191,8 @@
public void onFixedRotationFinished(int displayId) {}
@Override
- public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {}
+ public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+ List<Rect> unrestricted) {}
};
int[] displayIds = mAtm.mWindowManager.registerDisplayWindowListener(listener);
for (int i = 0; i < displayIds.length; i++) {
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
index 24fda17..27e8d69 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
@@ -16,6 +16,8 @@
package com.android.server.usage;
+import static android.app.ActivityManager.procStateToString;
+
import static com.android.server.usage.UsageStatsService.DEBUG_RESPONSE_STATS;
import android.annotation.ElapsedRealtimeLong;
@@ -23,6 +25,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManager.ProcessState;
import android.app.usage.BroadcastResponseStats;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -69,15 +72,26 @@
private SparseArray<SparseArray<UserBroadcastResponseStats>> mUserResponseStats =
new SparseArray<>();
+ private AppStandbyInternal mAppStandby;
+
+ BroadcastResponseStatsTracker(@NonNull AppStandbyInternal appStandby) {
+ mAppStandby = appStandby;
+ }
+
// TODO (206518114): Move all callbacks handling to a handler thread.
void reportBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
UserHandle targetUser, long idForResponseEvent,
- @ElapsedRealtimeLong long timestampMs) {
+ @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState) {
if (DEBUG_RESPONSE_STATS) {
- Slog.d(TAG, TextUtils.formatSimple(
- "reportBroadcastDispatchEvent; srcUid=%d, tgtPkg=%s, tgtUsr=%d, id=%d, ts=%s",
+ Slog.d(TAG, TextUtils.formatSimple("reportBroadcastDispatchEvent; "
+ + "srcUid=%d, tgtPkg=%s, tgtUsr=%d, id=%d, ts=%s, state=%s",
sourceUid, targetPackage, targetUser, idForResponseEvent,
- TimeUtils.formatDuration(timestampMs)));
+ TimeUtils.formatDuration(timestampMs), procStateToString(targetUidProcState)));
+ }
+ if (targetUidProcState <= mAppStandby.getBroadcastResponseFgThresholdState()) {
+ // No need to track the broadcast response state while the target app is
+ // in the foreground.
+ return;
}
synchronized (mLock) {
final LongSparseArray<BroadcastEvent> broadcastEvents =
@@ -132,8 +146,7 @@
if (dispatchTimestampMs >= timestampMs) {
continue;
}
- // TODO (206518114): Make the constant configurable.
- if (elapsedDurationMs <= 2 * 60 * 1000) {
+ if (elapsedDurationMs <= mAppStandby.getBroadcastResponseWindowDurationMs()) {
final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(i);
final BroadcastResponseStats responseStats =
getBroadcastResponseStats(broadcastEvent);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index e90d28a..98a41bc 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -42,6 +42,7 @@
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.ActivityManager.ProcessState;
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.PendingIntent;
@@ -281,7 +282,7 @@
mHandler = new H(BackgroundThread.get().getLooper());
mAppStandby = mInjector.getAppStandbyController(getContext());
- mResponseStatsTracker = new BroadcastResponseStatsTracker();
+ mResponseStatsTracker = new BroadcastResponseStatsTracker(mAppStandby);
mAppTimeLimit = new AppTimeLimitController(getContext(),
new AppTimeLimitController.TimeLimitCallbackListener() {
@@ -3042,9 +3043,9 @@
@Override
public void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage,
@NonNull UserHandle targetUser, long idForResponseEvent,
- @ElapsedRealtimeLong long timestampMs) {
+ @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState) {
mResponseStatsTracker.reportBroadcastDispatchEvent(sourceUid, targetPackage,
- targetUser, idForResponseEvent, timestampMs);
+ targetUser, idForResponseEvent, timestampMs, targetUidProcState);
}
@Override
diff --git a/services/wallpapereffectsgeneration/Android.bp b/services/wallpapereffectsgeneration/Android.bp
new file mode 100644
index 0000000..4dbb0fd
--- /dev/null
+++ b/services/wallpapereffectsgeneration/Android.bp
@@ -0,0 +1,22 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "services.wallpapereffectsgeneration-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.wallpapereffectsgeneration",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.wallpapereffectsgeneration-sources"],
+ libs: ["services.core"],
+}
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java
new file mode 100644
index 0000000..c228daf
--- /dev/null
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java
@@ -0,0 +1,113 @@
+/*
+ * 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.wallpapereffectsgeneration;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.service.wallpapereffectsgeneration.IWallpaperEffectsGenerationService;
+import android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+
+/**
+ * Proxy to the
+ * {@link android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService}
+ * implementation in another process.
+ */
+public class RemoteWallpaperEffectsGenerationService extends
+ AbstractMultiplePendingRequestsRemoteService<RemoteWallpaperEffectsGenerationService,
+ IWallpaperEffectsGenerationService> {
+
+ private static final String TAG =
+ RemoteWallpaperEffectsGenerationService.class.getSimpleName();
+
+ private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+ private final RemoteWallpaperEffectsGenerationServiceCallback mCallback;
+
+ public RemoteWallpaperEffectsGenerationService(Context context,
+ ComponentName componentName, int userId,
+ RemoteWallpaperEffectsGenerationServiceCallback callback,
+ boolean bindInstantServiceAllowed,
+ boolean verbose) {
+ super(context, WallpaperEffectsGenerationService.SERVICE_INTERFACE,
+ componentName, userId, callback,
+ context.getMainThreadHandler(),
+ bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
+ verbose, /* initialCapacity= */ 1);
+ mCallback = callback;
+ }
+
+ @Override
+ protected IWallpaperEffectsGenerationService getServiceInterface(IBinder service) {
+ return IWallpaperEffectsGenerationService.Stub.asInterface(service);
+ }
+
+ @Override
+ protected long getTimeoutIdleBindMillis() {
+ return PERMANENT_BOUND_TIMEOUT_MS;
+ }
+
+ @Override
+ protected long getRemoteRequestMillis() {
+ return TIMEOUT_REMOTE_REQUEST_MILLIS;
+ }
+
+ /**
+ * Schedules a request to bind to the remote service.
+ */
+ public void reconnect() {
+ super.scheduleBind();
+ }
+
+ /**
+ * Schedule async request on remote service.
+ */
+ public void scheduleOnResolvedService(
+ @NonNull AsyncRequest<IWallpaperEffectsGenerationService> request) {
+ scheduleAsyncRequest(request);
+ }
+
+ /**
+ * Execute async request on remote service immediately instead of sending it to Handler queue.
+ */
+ public void executeOnResolvedService(
+ @NonNull AsyncRequest<IWallpaperEffectsGenerationService> request) {
+ executeAsyncRequest(request);
+ }
+
+ /**
+ * Notifies server (WallpaperEffectsGenerationPerUserService) about unexpected events..
+ */
+ public interface RemoteWallpaperEffectsGenerationServiceCallback
+ extends VultureCallback<RemoteWallpaperEffectsGenerationService> {
+ /**
+ * Notifies change in connected state of the remote service.
+ */
+ void onConnectedStateChanged(boolean connected);
+ }
+
+ @Override // from AbstractRemoteService
+ protected void handleOnConnectedStateChanged(boolean connected) {
+ if (mCallback != null) {
+ mCallback.onConnectedStateChanged(connected);
+ }
+ }
+}
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerService.java
new file mode 100644
index 0000000..0d0b3e0
--- /dev/null
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerService.java
@@ -0,0 +1,185 @@
+/*
+ * 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.wallpapereffectsgeneration;
+
+import static android.Manifest.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.content.Context.WALLPAPER_EFFECTS_GENERATION_SERVICE;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+import android.app.wallpapereffectsgeneration.ICinematicEffectListener;
+import android.app.wallpapereffectsgeneration.IWallpaperEffectsGenerationManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.FileDescriptor;
+import java.util.function.Consumer;
+
+/**
+ * A service used to return wallpaper effect given a request.
+ */
+public class WallpaperEffectsGenerationManagerService extends
+ AbstractMasterSystemService<WallpaperEffectsGenerationManagerService,
+ WallpaperEffectsGenerationPerUserService> {
+ private static final String TAG =
+ WallpaperEffectsGenerationManagerService.class.getSimpleName();
+ private static final boolean DEBUG = false;
+ private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+ private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+ public WallpaperEffectsGenerationManagerService(Context context) {
+ super(context,
+ new FrameworkResourcesServiceNameResolver(context,
+ com.android.internal.R.string.config_defaultWallpaperEffectsGenerationService),
+ null,
+ PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ }
+
+ @Override
+ protected WallpaperEffectsGenerationPerUserService newServiceLocked(int resolvedUserId,
+ boolean disabled) {
+ return new WallpaperEffectsGenerationPerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(WALLPAPER_EFFECTS_GENERATION_SERVICE,
+ new WallpaperEffectsGenerationManagerStub());
+ }
+
+ @Override
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(MANAGE_WALLPAPER_EFFECTS_GENERATION, TAG);
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
+ final WallpaperEffectsGenerationPerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageUpdatedLocked();
+ }
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+ final WallpaperEffectsGenerationPerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageRestartedLocked();
+ }
+ }
+
+ @Override
+ protected int getMaximumTemporaryServiceDurationMs() {
+ return MAX_TEMP_SERVICE_DURATION_MS;
+ }
+
+ private class WallpaperEffectsGenerationManagerStub
+ extends IWallpaperEffectsGenerationManager.Stub {
+ @Override
+ public void generateCinematicEffect(@NonNull CinematicEffectRequest request,
+ @NonNull ICinematicEffectListener listener) {
+ if (!runForUserLocked("generateCinematicEffect", (service) ->
+ service.onGenerateCinematicEffectLocked(request, listener))) {
+ try {
+ listener.onCinematicEffectGenerated(
+ new CinematicEffectResponse.Builder(
+ CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_ERROR,
+ request.getTaskId()).build());
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.d(TAG, "fail to invoke cinematic effect listener for task["
+ + request.getTaskId() + "]");
+ }
+ }
+ }
+ }
+
+ @Override
+ public void returnCinematicEffectResponse(@NonNull CinematicEffectResponse response) {
+ runForUserLocked("returnCinematicResponse", (service) ->
+ service.onReturnCinematicEffectResponseLocked(response));
+ }
+
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) {
+ new WallpaperEffectsGenerationManagerServiceShellCommand(
+ WallpaperEffectsGenerationManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ /**
+ * Execute the operation for the user locked. Return true if
+ * WallpaperEffectsGenerationPerUserService is found for the user.
+ * Otherwise return false.
+ */
+ private boolean runForUserLocked(@NonNull final String func,
+ @NonNull final Consumer<WallpaperEffectsGenerationPerUserService> c) {
+ ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
+ final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ Binder.getCallingUserHandle().getIdentifier(), false, ALLOW_NON_FULL,
+ null, null);
+ if (DEBUG) {
+ Slog.d(TAG, "runForUserLocked:" + func + " from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ }
+ Context ctx = getContext();
+ if (!(ctx.checkCallingPermission(MANAGE_WALLPAPER_EFFECTS_GENERATION)
+ == PERMISSION_GRANTED
+ || mServiceNameResolver.isTemporary(userId)
+ || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
+ String msg = "Permission Denial: Cannot call " + func + " from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ final long origId = Binder.clearCallingIdentity();
+ boolean accepted = false;
+ try {
+ synchronized (mLock) {
+ final WallpaperEffectsGenerationPerUserService service =
+ getServiceForUserLocked(userId);
+ if (service != null) {
+ accepted = true;
+ c.accept(service);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ return accepted;
+ }
+ }
+}
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerServiceShellCommand.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerServiceShellCommand.java
new file mode 100644
index 0000000..fc6f75f
--- /dev/null
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerServiceShellCommand.java
@@ -0,0 +1,85 @@
+/*
+ * 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.wallpapereffectsgeneration;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the WallpaperEffectsGenerationService.
+ */
+public class WallpaperEffectsGenerationManagerServiceShellCommand extends ShellCommand {
+
+ private static final String TAG =
+ WallpaperEffectsGenerationManagerServiceShellCommand.class.getSimpleName();
+
+ private final WallpaperEffectsGenerationManagerService mService;
+
+ public WallpaperEffectsGenerationManagerServiceShellCommand(
+ @NonNull WallpaperEffectsGenerationManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "set": {
+ final String what = getNextArgRequired();
+ switch (what) {
+ case "temporary-service": {
+ final int userId = Integer.parseInt(getNextArgRequired());
+ String serviceName = getNextArg();
+ if (serviceName == null) {
+ mService.resetTemporaryService(userId);
+ pw.println("WallpaperEffectsGenerationService temporarily reset. ");
+ return 0;
+ }
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryService(userId, serviceName, duration);
+ pw.println("WallpaperEffectsGenerationService temporarily set to "
+ + serviceName + " for " + duration + "ms");
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ try (PrintWriter pw = getOutPrintWriter()) {
+ pw.println("WallpaperEffectsGenerationService commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println("");
+ pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implemtation.");
+ pw.println(" To reset, call with just the USER_ID argument.");
+ pw.println("");
+ }
+ }
+}
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
new file mode 100644
index 0000000..d541051
--- /dev/null
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
@@ -0,0 +1,274 @@
+/*
+ * 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.wallpapereffectsgeneration;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+import android.app.wallpapereffectsgeneration.ICinematicEffectListener;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/**
+ * Per-user instance of {@link WallpaperEffectsGenerationManagerService}.
+ */
+public class WallpaperEffectsGenerationPerUserService extends
+ AbstractPerUserSystemService<WallpaperEffectsGenerationPerUserService,
+ WallpaperEffectsGenerationManagerService> implements
+ RemoteWallpaperEffectsGenerationService.RemoteWallpaperEffectsGenerationServiceCallback {
+
+ private static final String TAG =
+ WallpaperEffectsGenerationPerUserService.class.getSimpleName();
+
+ @GuardedBy("mLock")
+ private CinematicEffectListenerWrapper mCinematicEffectListenerWrapper;
+
+ @Nullable
+ @GuardedBy("mLock")
+ private RemoteWallpaperEffectsGenerationService mRemoteService;
+
+ protected WallpaperEffectsGenerationPerUserService(
+ WallpaperEffectsGenerationManagerService master,
+ Object lock, int userId) {
+ super(master, lock, userId);
+ }
+
+ @Override // from PerUserSystemService
+ protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+ throws NameNotFoundException {
+ ServiceInfo si;
+ try {
+ si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ throw new NameNotFoundException("Could not get service for " + serviceComponent);
+ }
+ if (!Manifest.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE.equals(si.permission)) {
+ Slog.w(TAG, "WallpaperEffectsGenerationService from '" + si.packageName
+ + "' does not require permission "
+ + Manifest.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE);
+ throw new SecurityException("Service does not require permission "
+ + Manifest.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE);
+ }
+ return si;
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected boolean updateLocked(boolean disabled) {
+ final boolean enabledChanged = super.updateLocked(disabled);
+ updateRemoteServiceLocked();
+ return enabledChanged;
+ }
+
+ /**
+ * Notifies the service of a new cinematic effect generation request.
+ */
+ @GuardedBy("mLock")
+ public void onGenerateCinematicEffectLocked(
+ @NonNull CinematicEffectRequest cinematicEffectRequest,
+ @NonNull ICinematicEffectListener cinematicEffectListener) {
+ String newTaskId = cinematicEffectRequest.getTaskId();
+ // Previous request is still being processed.
+ if (mCinematicEffectListenerWrapper != null) {
+ if (mCinematicEffectListenerWrapper.mTaskId.equals(newTaskId)) {
+ invokeCinematicListenerAndCleanup(
+ new CinematicEffectResponse.Builder(
+ CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_PENDING, newTaskId)
+ .build()
+ );
+ } else {
+ invokeCinematicListenerAndCleanup(
+ new CinematicEffectResponse.Builder(
+ CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS,
+ newTaskId).build()
+ );
+ }
+ return;
+ }
+ RemoteWallpaperEffectsGenerationService remoteService = ensureRemoteServiceLocked();
+ if (remoteService != null) {
+ remoteService.executeOnResolvedService(
+ s -> s.onGenerateCinematicEffect(cinematicEffectRequest));
+ mCinematicEffectListenerWrapper =
+ new CinematicEffectListenerWrapper(newTaskId, cinematicEffectListener);
+ } else {
+ if (isDebug()) {
+ Slog.d(TAG, "Remote service not found");
+ }
+ try {
+ cinematicEffectListener.onCinematicEffectGenerated(
+ createErrorCinematicEffectResponse(newTaskId));
+ } catch (RemoteException e) {
+ if (isDebug()) {
+ Slog.d(TAG, "Failed to invoke cinematic effect listener for task [" + newTaskId
+ + "]");
+ }
+ }
+ }
+ }
+
+ /**
+ * Notifies the service of a generated cinematic effect response.
+ */
+ @GuardedBy("mLock")
+ public void onReturnCinematicEffectResponseLocked(
+ @NonNull CinematicEffectResponse cinematicEffectResponse) {
+ invokeCinematicListenerAndCleanup(cinematicEffectResponse);
+ }
+
+ @GuardedBy("mLock")
+ private void updateRemoteServiceLocked() {
+ if (mRemoteService != null) {
+ mRemoteService.destroy();
+ mRemoteService = null;
+ }
+ // End existing response and clean up listener for next request.
+ if (mCinematicEffectListenerWrapper != null) {
+ invokeCinematicListenerAndCleanup(
+ createErrorCinematicEffectResponse(mCinematicEffectListenerWrapper.mTaskId));
+ }
+
+ }
+
+ void onPackageUpdatedLocked() {
+ if (isDebug()) {
+ Slog.v(TAG, "onPackageUpdatedLocked()");
+ }
+ destroyAndRebindRemoteService();
+ }
+
+ void onPackageRestartedLocked() {
+ if (isDebug()) {
+ Slog.v(TAG, "onPackageRestartedLocked()");
+ }
+ destroyAndRebindRemoteService();
+ }
+
+ private void destroyAndRebindRemoteService() {
+ if (mRemoteService == null) {
+ return;
+ }
+
+ if (isDebug()) {
+ Slog.d(TAG, "Destroying the old remote service.");
+ }
+ mRemoteService.destroy();
+ mRemoteService = null;
+ mRemoteService = ensureRemoteServiceLocked();
+ if (mRemoteService != null) {
+ if (isDebug()) {
+ Slog.d(TAG, "Rebinding to the new remote service.");
+ }
+ mRemoteService.reconnect();
+ }
+ // Clean up listener for next request.
+ if (mCinematicEffectListenerWrapper != null) {
+ invokeCinematicListenerAndCleanup(
+ createErrorCinematicEffectResponse(mCinematicEffectListenerWrapper.mTaskId));
+ }
+ }
+
+ private CinematicEffectResponse createErrorCinematicEffectResponse(String taskId) {
+ return new CinematicEffectResponse.Builder(
+ CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_ERROR,
+ taskId).build();
+ }
+
+ @GuardedBy("mLock")
+ private void invokeCinematicListenerAndCleanup(
+ CinematicEffectResponse cinematicEffectResponse) {
+ try {
+ if (mCinematicEffectListenerWrapper != null
+ && mCinematicEffectListenerWrapper.mListener != null) {
+ mCinematicEffectListenerWrapper.mListener.onCinematicEffectGenerated(
+ cinematicEffectResponse);
+ } else {
+ if (isDebug()) {
+ Slog.w(TAG, "Cinematic effect listener not found for task["
+ + mCinematicEffectListenerWrapper.mTaskId + "]");
+ }
+ }
+ } catch (RemoteException e) {
+ if (isDebug()) {
+ Slog.w(TAG, "Error invoking cinematic effect listener for task["
+ + mCinematicEffectListenerWrapper.mTaskId + "]");
+ }
+ } finally {
+ mCinematicEffectListenerWrapper = null;
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteWallpaperEffectsGenerationService ensureRemoteServiceLocked() {
+ if (mRemoteService == null) {
+ final String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "ensureRemoteServiceLocked(): not set");
+ }
+ return null;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+ mRemoteService = new RemoteWallpaperEffectsGenerationService(getContext(),
+ serviceComponent, mUserId, this,
+ mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ }
+
+ return mRemoteService;
+ }
+
+ @Override // from RemoteWallpaperEffectsGenerationService
+ public void onServiceDied(RemoteWallpaperEffectsGenerationService service) {
+ Slog.w(TAG, "remote wallpaper effects generation service died");
+ updateRemoteServiceLocked();
+ }
+
+ @Override // from RemoteWallpaperEffectsGenerationService
+ public void onConnectedStateChanged(boolean connected) {
+ if (!connected) {
+ Slog.w(TAG, "remote wallpaper effects generation service disconnected");
+ updateRemoteServiceLocked();
+ }
+ }
+
+ private static final class CinematicEffectListenerWrapper {
+ @NonNull
+ private final String mTaskId;
+ @NonNull
+ private final ICinematicEffectListener mListener;
+
+ CinematicEffectListenerWrapper(
+ @NonNull final String taskId,
+ @NonNull final ICinematicEffectListener listener) {
+ mTaskId = taskId;
+ mListener = listener;
+ }
+ }
+}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index c5fc436..27d423b 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -3156,15 +3157,27 @@
}
/**
- * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
- * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
- * call created using
- * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
+ * Calls of this type are created using
+ * {@link TelecomManager#addNewUnknownCall(PhoneAccountHandle, Bundle)}. Unknown calls
+ * are used for representing calls which become known to the {@link ConnectionService}
+ * midway through the call.
+ *
+ * For example, a call transferred from one device to answer would surface as an active
+ * call in Telecom instead of going through a typical Ringing to Active transition, or
+ * Dialing to Active transition.
+ *
+ * A {@link ConnectionService} can return {@code null} (the default behavior)
+ * if it is not able to handle a request for the requested unknown connection.
+ *
+ * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
*
* @hide
*/
- public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
- ConnectionRequest request) {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public @Nullable Connection onCreateUnknownConnection(
+ @NonNull PhoneAccountHandle connectionManagerPhoneAccount,
+ @NonNull ConnectionRequest request) {
return null;
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a74930b..3c277b7 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4540,9 +4540,7 @@
* Passing this value as {@link #KEY_SUBSCRIPTION_GROUP_UUID_STRING} will remove the
* subscription from a group instead of adding it to a group.
*
- * TODO: Expose in a future release.
- *
- * @hide
+ * <p>This value will work all the way back to {@link android.os.Build.VERSION_CODES#Q}.
*/
public static final String REMOVE_GROUP_UUID_STRING = "00000000-0000-0000-0000-000000000000";
@@ -4555,9 +4553,7 @@
* <p>If set to {@link #REMOVE_GROUP_UUID_STRING}, then the subscription will be removed from
* its current group.
*
- * TODO: unhide this key.
- *
- * @hide
+ * <p>This key will work all the way back to {@link android.os.Build.VERSION_CODES#Q}.
*/
public static final String KEY_SUBSCRIPTION_GROUP_UUID_STRING =
"subscription_group_uuid_string";
@@ -4605,17 +4601,15 @@
"data_switch_validation_min_gap_long";
/**
- * A boolean property indicating whether this subscription should be managed as an opportunistic
- * subscription.
- *
- * If true, then this subscription will be selected based on available coverage and will not be
- * available for a user in settings menus for selecting macro network providers. If unset,
- * defaults to “false”.
- *
- * TODO: unhide this key.
- *
- * @hide
- */
+ * A boolean property indicating whether this subscription should be managed as an opportunistic
+ * subscription.
+ *
+ * If true, then this subscription will be selected based on available coverage and will not be
+ * available for a user in settings menus for selecting macro network providers. If unset,
+ * defaults to “false”.
+ *
+ * <p>This key will work all the way back to {@link android.os.Build.VERSION_CODES#Q}.
+ */
public static final String KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL =
"is_opportunistic_subscription_bool";
@@ -8920,7 +8914,7 @@
sDefaults.putBoolean(KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL, true);
sDefaults.putBoolean(KEY_HIDE_ENABLE_2G, false);
sDefaults.putStringArray(KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY,
- new String[]{"ia", "default", "mms", "dun"});
+ new String[]{"ia", "default"});
sDefaults.putBoolean(KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL, false);
sDefaults.putBoolean(KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL, false);
sDefaults.putBoolean(KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL, true);
diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
index 48bfd6f..6290292 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.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 @@
@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 @@
}
@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 @@
@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 @@
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/tools/lint/README.md b/tools/lint/README.md
index 2b6d65b..b534b62 100644
--- a/tools/lint/README.md
+++ b/tools/lint/README.md
@@ -40,6 +40,9 @@
- If you want to build lint reports for more than 1 module and they include a common module in their
`defaults` field, e.g. `platform_service_defaults`, you can add the `lint` property to that common
module instead of adding it in every module.
+- If you want to run a single lint type, use the `ANDROID_LINT_CHECK`
+ environment variable with the id of the lint. For example:
+ `ANDROID_LINT_CHECK=UnusedTokenOfOriginalCallingIdentity m out/[...]/lint-report.html`
## Create or update a baseline